Skip to content

Commit

Permalink
added: flycheck-haskell
Browse files Browse the repository at this point in the history
  • Loading branch information
ncaq committed May 8, 2015
1 parent 8b757d5 commit a01aba3
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 0 deletions.
34 changes: 34 additions & 0 deletions elpa/flycheck-haskell-20150406.327/flycheck-haskell-autoloads.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
;;; flycheck-haskell-autoloads.el --- automatically extracted autoloads
;;
;;; Code:
(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))

;;;### (autoloads nil "flycheck-haskell" "flycheck-haskell.el" (21836
;;;;;; 37615 467297 803000))
;;; Generated autoloads from flycheck-haskell.el

(autoload 'flycheck-haskell-setup "flycheck-haskell" "\
Setup Haskell support for Flycheck.
If the current file is part of a Cabal project, configure
Flycheck to take the module paths of the Cabal projects into
account.
Also search for Cabal sandboxes and add them to the module search
path as well.
\(fn)" nil nil)

;;;***

;;;### (autoloads nil nil ("flycheck-haskell-pkg.el") (21836 37615
;;;;;; 481651 694000))

;;;***

;; Local Variables:
;; version-control: never
;; no-byte-compile: t
;; no-update-autoloads: t
;; End:
;;; flycheck-haskell-autoloads.el ends here
10 changes: 10 additions & 0 deletions elpa/flycheck-haskell-20150406.327/flycheck-haskell-pkg.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(define-package "flycheck-haskell" "20150406.327" "Flycheck: Cabal projects and sandboxes"
'((flycheck "0.22")
(haskell-mode "13.7")
(dash "2.4.0")
(let-alist "1.0.1"))
:url "https://github.com/flycheck/flycheck-haskell" :keywords
'("tools" "convenience"))
;; Local Variables:
;; no-byte-compile: t
;; End:
221 changes: 221 additions & 0 deletions elpa/flycheck-haskell-20150406.327/flycheck-haskell.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
;;; flycheck-haskell.el --- Flycheck: Cabal projects and sandboxes -*- lexical-binding: t; -*-

;; Copyright (C) 2014, 2015 Sebastian Wiesner <swiesner@lunaryorn.com>
;; Copyright (C) 2014 Gracjan Polak <gracjanpolak@gmail.com>

;; Author: Sebastian Wiesner <swiesner@lunaryorn.com>
;; URL: https://github.com/flycheck/flycheck-haskell
;; Keywords: tools, convenience
;; Version: 0.7-cvs
;; Package-Requires: ((flycheck "0.22") (haskell-mode "13.7") (dash "2.4.0") (let-alist "1.0.1"))

;; This file is not part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; Configure Haskell syntax checking by Flycheck.

;;;; Cabal support

;; Try to find Cabal project files for Haskell buffers, and configure the
;; Haskell syntax checkers in Flycheck according to the contents of the Cabal
;; file:
;;
;; - Add all source directories to the GHC search path
;; - Add build directories from Cabal to the GHC search path to speed up
;; checking and support non-Haskell modules such as hsc files
;; - Add auto-generated files from Cabal to the GHC search path
;; - Set the language from Cabal
;; - Enable language extensions from Cabal

;;;; Cabal sandboxes

;; Try to find a Cabal sandbox configuration for this project, and configure the
;; Haskell syntax checkers in Flycheck to use the package database from the
;; Sandbox.

;;;; Setup

;; (add-hook 'flycheck-mode-hook #'flycheck-haskell-setup)

;;; Code:

(eval-when-compile
(require 'rx)
(require 'let-alist))

(require 'haskell-cabal)
(require 'flycheck)
(require 'dash)


;;; Customization

(defgroup flycheck-haskell nil
"Haskell support for Flycheck."
:prefix "flycheck-haskell-"
:group 'flycheck
:link '(url-link :tag "Github" "https://github.com/flycheck/flycheck-haskell"))

(defcustom flycheck-haskell-runhaskell "runhaskell"
"Path to the `runhaskell' executable.
This library uses `runhaskell' to run various Haskell helper
scripts to extract information from Cabal files."
:type `(file :must-match t)
:group 'flycheck-haskell)


;;; Cabal support
(defconst flycheck-haskell-helper
(expand-file-name "get-cabal-configuration.hs"
(file-name-directory (if load-in-progress
load-file-name
(buffer-file-name))))
"The helper to dump the Cabal configuration.")

(defconst flycheck-haskell-config-cache (make-hash-table :test 'equal)
"Cache of Cabal configuration.
A hash table, mapping the name of a cabal file to a
cons-cell `(MODTIME . CONFIG)', where MODTIME is the modification
time of the cabal file, and CONFIG the extracted configuration.")

(defun flycheck-haskell-clear-config-cache ()
"Clear the cache of configurations."
(interactive)
(clrhash flycheck-haskell-config-cache))

(defun flycheck-haskell-get-cached-configuration (cabal-file)
"Get the cached configuration for CABAL-FILE.
Return the cached configuration, or nil, if there is no cache
entry, or if the cache entry is outdated."
(pcase-let* ((cache-entry (gethash cabal-file flycheck-haskell-config-cache))
(`(,modtime . ,config) cache-entry))
(when (and modtime (file-exists-p cabal-file))
(let ((current-modtime (nth 5 (file-attributes cabal-file))))
(if (time-less-p modtime current-modtime)
;; The entry is outdated, drop it. `remhash' always
;; returns nil, so we are safe to use it here.
(remhash cabal-file flycheck-haskell-config-cache)
;; The configuration is up to date, use it
config)))))

(defun flycheck-haskell-read-cabal-configuration (cabal-file)
"Read the Cabal configuration from CABAL-FILE."
(with-temp-buffer
(let ((result (call-process flycheck-haskell-runhaskell nil t nil
flycheck-haskell-helper cabal-file)))
(when (= result 0)
(goto-char (point-min))
(read (current-buffer))))))

(defun flycheck-haskell-read-and-cache-configuration (cabal-file)
"Read and cache configuration from CABAL-FILE.
Return the configuration."
(let ((modtime (nth 5 (file-attributes cabal-file)))
(config (flycheck-haskell-read-cabal-configuration cabal-file)))
(puthash cabal-file (cons modtime config) flycheck-haskell-config-cache)
config))

(defun flycheck-haskell-get-configuration (cabal-file)
"Get the Cabal configuration from CABAL-FILE.
Get the configuration either from our cache, or by reading the
CABAL-FILE.
Return the configuration."
(or (flycheck-haskell-get-cached-configuration cabal-file)
(flycheck-haskell-read-and-cache-configuration cabal-file)))

(defconst flycheck-haskell-sandbox-config "cabal.sandbox.config"
"The file name of a Cabal sandbox configuration.")

(defconst flycheck-haskell-package-db-re
(rx line-start (zero-or-more (any space)) "package-db:"
(zero-or-more (any space))
(group (one-or-more (not (any space))))
(zero-or-more (any space) line-end))
"Regular expression to parse the package db directory.")

(defun flycheck-haskell-get-package-db (sandbox-config-file)
"Get the package database directory from SANDBOX-CONFIG-FILE.
Return the package database directory as string, or nil, if the
database was not found."
(with-temp-buffer
(insert-file-contents sandbox-config-file)
(goto-char (point-min))
(when (re-search-forward flycheck-haskell-package-db-re nil 'noerror)
(match-string 1))))

(defun flycheck-haskell-find-sandbox-config ()
"Find Cabal sandbox configuration for the current buffer.
Return the absolute path of the sandbox configuration file as
string, or nil, if no sandbox configuration file was found."
(-when-let (root-dir (locate-dominating-file (buffer-file-name)
flycheck-haskell-sandbox-config))
(expand-file-name flycheck-haskell-sandbox-config root-dir)))

(defun flycheck-haskell-process-configuration (config)
"Process the a Cabal CONFIG."
(let-alist config
(setq-local flycheck-ghc-search-path
(append .build-directories .source-directories
flycheck-ghc-search-path))
(setq-local flycheck-ghc-language-extensions
(append .extensions .languages
flycheck-ghc-language-extensions))
(setq-local flycheck-ghc-args
(append .other-options flycheck-ghc-args))))

(defun flycheck-haskell-configure ()
"Set paths and package database for the current project."
(interactive)
(when (and (buffer-file-name) (file-directory-p default-directory))
(-when-let* ((cabal-file (haskell-cabal-find-file))
(config (flycheck-haskell-get-configuration cabal-file)))
(flycheck-haskell-process-configuration config))

(-when-let* ((config (flycheck-haskell-find-sandbox-config))
(package-db (flycheck-haskell-get-package-db config)))
(setq-local flycheck-ghc-package-databases
(cons package-db flycheck-ghc-package-databases))
(setq-local flycheck-ghc-no-user-package-database t))))

;;;###autoload
(defun flycheck-haskell-setup ()
"Setup Haskell support for Flycheck.
If the current file is part of a Cabal project, configure
Flycheck to take the module paths of the Cabal projects into
account.
Also search for Cabal sandboxes and add them to the module search
path as well."
(add-hook 'hack-local-variables-hook #'flycheck-haskell-configure))

(provide 'flycheck-haskell)

;; Local Variables:
;; indent-tabs-mode: nil
;; coding: utf-8
;; End:

;;; flycheck-haskell.el ends here
Binary file not shown.
128 changes: 128 additions & 0 deletions elpa/flycheck-haskell-20150406.327/get-cabal-configuration.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
-- Copyright (C) 2014 Sebastian Wiesner <swiesner@lunaryorn.com>
-- Copyright (C) 2014 Gracjan Polak <gracjanpolak@gmail.com>

-- This file is not part of GNU Emacs.

-- This program is free software; you can redistribute it and/or modify it under
-- the terms of the GNU General Public License as published by the Free Software
-- Foundation, either version 3 of the License, or (at your option) any later
-- version.

-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-- details.

-- You should have received a copy of the GNU General Public License along with
-- this program. If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}

import Control.Monad (liftM)
import Data.List (nub, isPrefixOf)
import Data.Maybe (listToMaybe)
import Distribution.Compiler (CompilerFlavor(GHC))
import Distribution.Package (PackageName(..),Dependency(..))
import Distribution.PackageDescription (PackageDescription(..),allBuildInfo
,BuildInfo(..)
,usedExtensions,allLanguages
,hcOptions
,exeName)
import Distribution.PackageDescription.Configuration (flattenPackageDescription)
import Distribution.PackageDescription.Parse (readPackageDescription)
import Distribution.Simple.BuildPaths (defaultDistPref)
import Distribution.Verbosity (silent)
import Language.Haskell.Extension (Extension(..),Language(..))
import System.Environment (getArgs)
import System.Exit (exitFailure)
import System.FilePath ((</>),dropFileName,normalise)

data Sexp = SList [Sexp]
| SString String
| SSymbol String

sym :: String -> Sexp
sym = SSymbol

instance Show Sexp where
show (SSymbol s) = s
show (SString s) = show s -- Poor man's escaping
show (SList s) = "(" ++ unwords (map show s) ++ ")"

class ToSexp a where
toSexp :: a -> Sexp

instance ToSexp String where
toSexp = SString

instance ToSexp Extension where
toSexp (EnableExtension ext) = toSexp (show ext)
toSexp (DisableExtension ext) = toSexp ("No" ++ show ext)
toSexp (UnknownExtension ext) = toSexp ext

instance ToSexp Language where
toSexp (UnknownLanguage lang) = toSexp lang
toSexp lang = toSexp (show lang)

instance ToSexp Dependency where
toSexp (Dependency (PackageName dependency) _) = toSexp dependency

instance ToSexp Sexp where
toSexp = id

cons :: (ToSexp a, ToSexp b) => a -> [b] -> Sexp
cons h t = SList (toSexp h : map toSexp t)

getBuildDirectories :: PackageDescription -> FilePath -> [String]
getBuildDirectories pkgDesc cabalDir = case library pkgDesc of
Just _ -> buildDir : buildDirs
Nothing -> buildDirs
where distDir = cabalDir </> defaultDistPref
buildDir = distDir </> "build"
autogenDir = buildDir </> "autogen"
executableBuildDir e = buildDir </> exeName e </> (exeName e ++ "-tmp")
buildDirs = autogenDir : map executableBuildDir (executables pkgDesc)

getSourceDirectories :: [BuildInfo] -> FilePath -> [String]
getSourceDirectories buildInfo cabalDir =
map (cabalDir </>) (concatMap hsSourceDirs buildInfo)

usefulOptions :: [String]
usefulOptions = ["-W", "-w", "-Wall", "-fglasgow-exts", "-fpackage-trust", "-fhelpful-errors", "-F", "-cpp"]

usefulOptionPrefixes :: [String]
usefulOptionPrefixes = ["-fwarn-", "-fno-warn-", "-fcontext-stack=", "-firrefutable-tuples", "-D", "-U", "-I", "-fplugin=", "-fplugin-opt=", "-pgm", "-opt"]

isFlycheckUsefulOption :: String -> Bool
isFlycheckUsefulOption opt = elem opt usefulOptions || any (`isPrefixOf` opt) usefulOptionPrefixes

dumpPackageDescription :: PackageDescription -> FilePath -> Sexp
dumpPackageDescription pkgDesc cabalFile = SList [
cons (sym "build-directories") buildDirs
, cons (sym "source-directories") sourceDirs
, cons (sym "extensions") exts
, cons (sym "languages") langs
, cons (sym "dependencies") deps
, cons (sym "other-options") otherOptions
]
where cabalDir = dropFileName cabalFile
buildInfo = allBuildInfo pkgDesc
buildDirs = nub (map normalise (getBuildDirectories pkgDesc cabalDir))
sourceDirs = nub (map normalise (getSourceDirectories buildInfo cabalDir))
exts = nub (concatMap usedExtensions buildInfo)
langs = nub (concatMap allLanguages buildInfo)
deps = nub (buildDepends pkgDesc)
otherOptions = nub (filter isFlycheckUsefulOption (concatMap (hcOptions GHC) buildInfo))

dumpCabalConfiguration :: String -> IO ()
dumpCabalConfiguration cabalFile = do
pkgDesc <- liftM flattenPackageDescription
(readPackageDescription silent cabalFile)
print (dumpPackageDescription pkgDesc cabalFile)

main :: IO ()
main = do
args <- getArgs
let cabalFile = listToMaybe args
maybe exitFailure dumpCabalConfiguration cabalFile
Loading

0 comments on commit a01aba3

Please sign in to comment.