git-linter/pre-commit

184 lines
6.2 KiB
Plaintext
Raw Normal View History

2015-05-23 06:11:42 +00:00
#!/usr/bin/env hy
(def *version* "0.0.2")
(import os re subprocess sys)
; pccs (pre-commit configs) should be a directory under your .git
; where you store the RCs for your various linters. If you want to
; use a global one, you'll have to edit the configuration entries
; below.
(def *config-path* (os.path.join (.get os.environ "GIT_DIR" "./.git") "pccs"))
(def *modified* (.compile re "^[MA]\s+(?P<name>.*)$"))
(def *checks*
[
; {
; "output" "Checking for debugger commands in Javascript..."
; "command" "grep -n debugger {filename}"
; "match_files" [".*\.js$"]
; "print_filename" True
; "error_condition" "output"
; }
{
"output" "Running Jshint..."
"command" "jshint -c {config_path}/jshint.rc {filename}"
"match_files" [".*\.js$"]
"print_filename" False
"error_condition" "error"
}
{
"output" "Running Coffeelint..."
"command" "coffeelint {filename}"
"match_files" [".*\.coffee$"]
"print_filename" False
"error_condition" "error"
}
{
"output" "Running JSCS..."
"command" "jscs -c {config_path}/jscs.rc {filename}"
"match_files" [".*\.js$"]
"print_filename" False
"error_condition" "error"
}
{
"output" "Running pep8..."
"command" "pep8 -r --ignore=E501,W293,W391 {filename}"
"match_files" [".*\.py$"]
"print_filename" False
"error_condition" "error"
}
{
"output" "Running xmllint..."
"command" "xmllint {filename}"
"match_files" [".*\.xml"]
"print_filename" False
"error_condition" "error"
}
]
)
2015-05-26 19:50:12 +00:00
(defn get-git-value [cmd]
2015-05-23 06:11:42 +00:00
(let [[fullcmd (+ ["git"] cmd)]
[process (subprocess.Popen fullcmd
:stdout subprocess.PIPE
:stderr subprocess.PIPE)]
[(, out err) (.communicate process)]]
(, out err process.returncode)))
2015-05-26 19:50:12 +00:00
(defn run-git-command [cmd]
2015-05-23 06:11:42 +00:00
(let [[fullcmd (+ ["git"] cmd)]]
(subprocess.call fullcmd
:stdout subprocess.PIPE
:stderr subprocess.PIPE)))
2015-05-26 19:50:12 +00:00
(defn get-shell-value [fullcmd]
(let [[process (subprocess.Popen fullcmd
2015-05-23 06:11:42 +00:00
:stdout subprocess.PIPE
:stderr subprocess.PIPE
:shell True)]
[(, out err) (.communicate process)]]
(, out err process.returncode)))
2015-05-26 19:50:12 +00:00
(defn derive-max-code [code-pairs]
(reduce
(fn [m i] (if (> (abs (get i 0)) (abs m)) (get i 0) m))
code-pairs 0))
2015-05-23 06:11:42 +00:00
2015-05-26 19:50:12 +00:00
(defn derive-message-bodies [code-pairs]
2015-05-23 06:11:42 +00:00
(lmap (fn [i] (get i 1)) code-pairs))
(defn matches-file [filename match-files]
(any (map (fn [match-file] (-> (.compile re match-file)
(.match filename)))
match-files)))
; Hy is overeager to return an iterators which is consumed during
; later traversal. lmap returns a concrete list instead.
(defn lmap (pred iter) (list (map pred iter)))
(defn run-external-checker [filename check]
(let [[cmd (-> (get check "command") (.format
:filename filename
:config_path *config-path*))]
2015-05-26 19:50:12 +00:00
[(, out err returncode) (get-shell-value cmd)]]
2015-05-23 06:11:42 +00:00
(if (or (and out (= (.get check "error_condition" "error") "output"))
err
(not (= returncode 0)))
(let [[prefix (if (get check "print_filename")
(.format "\t{}:" filename)
"\t")]
[output (+
(lmap (fn [line] (.format "{}{}" prefix (.decode line "utf-8")))
(.splitlines out))
(if err [err] []))]]
[(or returncode 1) output])
[0 []])))
2015-05-26 19:50:12 +00:00
(defn check-scan-wanted [filename check]
2015-05-23 06:11:42 +00:00
(cond [(and (in "match_files" check)
2015-05-26 19:50:12 +00:00
(not (matches-file filename (get check "match_files")))) false]
2015-05-23 06:11:42 +00:00
[(and (in "ignore_files" check)
2015-05-26 19:50:12 +00:00
(matches-file filename (get check "ignore_files"))) false]
[true true]))
2015-05-23 06:11:42 +00:00
(defn check-files [filenames check]
2015-05-26 19:50:12 +00:00
(let [[filenames-to-check
(lmap (fn [filename] (check-scan-wanted filename check)) filenames)]
[scan-results
(lmap (fn [filename]
(run-external-checker filename check)) filenames-to-check)]
[messages (+ [(get check "output")] (derive-message-bodies scan-results))]]
[(derive-max-code scan-results) messages]))
(defn gather-all-filenames []
2015-05-23 06:11:42 +00:00
(let [[build-filenames
(fn [filenames]
(map
(fn [f] (os.path.join (get filenames 0) f)) (get filenames 2)))]]
2015-05-26 19:50:12 +00:00
(list (flatten (list-comp (build-filenames o) [o (.walk os ".")])))))
2015-05-23 06:11:42 +00:00
; I removed the originally recommended "-u" command from stash; it was
; "cleaning up" far too zealously, deleting my node_modules directory
2015-05-26 19:50:12 +00:00
(defn gather-staged-filenames [against]
2015-05-23 06:11:42 +00:00
(let [[(, out err returncode)
2015-05-26 19:50:12 +00:00
(get-git-value ["diff-index" "--name-status" against])]
2015-05-23 06:11:42 +00:00
[lines (.splitlines out)]
[matcher (fn [line] (.match *modified* (.decode line "utf-8")))]]
2015-05-26 19:50:12 +00:00
(list (filter
2015-05-23 06:11:42 +00:00
(fn [x] (not (= x "")))
2015-05-26 19:50:12 +00:00
(list-comp (.group match "name") [match (map matcher lines)] match)))))
2015-05-23 06:11:42 +00:00
2015-05-26 19:50:12 +00:00
(defn scan-files [scan-all-files against]
2015-05-23 06:11:42 +00:00
(do
2015-05-26 19:50:12 +00:00
(run-git-command ["stash" "--keep-index"])
(let [[filenames-to-scan
(if scan-all-files
(gather-all-filenames)
(gather-staged-filenames against))]
[results-of-scan
<<<<<<< Updated upstream
(lmap (fn [check] (check-files filenames-to-scan check)) *checks*)]
=======
(lmap (fn [check] (scan-files filenames-to-scan check)) *checks*)]
>>>>>>> Stashed changes
[exit-code (derive-max-code results-of-scan)]
[messages (flatten (derive-message-bodies results-of-scan))]]
(do
(for [line messages] (print line))
(run-git-command ["reset" "--hard"])
(run-git-command ["stash" "pop" "--quiet" "--index"])
exit-code))))
2015-05-23 06:11:42 +00:00
(defn get-head-tag []
2015-05-26 19:50:12 +00:00
(let [[empty-repository-hash "4b825dc642cb6eb9a060e54bf8d69288fbee4904"]
[(, out err returncode) (get-git-value ["rev-parse" "--verify HEAD"])]]
(if err empty-repository-hash "HEAD")))
2015-05-23 06:11:42 +00:00
(defmain [&rest args]
2015-05-26 19:50:12 +00:00
(let [[scan-all-files (and (> (len args) 1) (= (get args 2) "--all-files"))]]
(sys.exit (scan-files scan-all-files (get-head-tag)))))