182 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Hy
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Hy
		
	
	
		
			Executable File
		
	
	
| #!/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 *git-modified-pattern* (.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"
 | |
|     }
 | |
|    ]
 | |
|   )
 | |
| 
 | |
| (defn get-git-response [cmd]
 | |
|   (let [[fullcmd (+ ["git"] cmd)]
 | |
|         [process (subprocess.Popen fullcmd
 | |
|                                    :stdout subprocess.PIPE
 | |
|                                    :stderr subprocess.PIPE)]
 | |
|         [(, out err) (.communicate process)]]
 | |
|     (, out err process.returncode)))
 | |
| 
 | |
| (defn run-git-command [cmd]
 | |
|   (let [[fullcmd (+ ["git"] cmd)]]
 | |
|     (subprocess.call fullcmd
 | |
|                      :stdout subprocess.PIPE
 | |
|                      :stderr subprocess.PIPE)))
 | |
| 
 | |
| (defn get-shell-response [fullcmd]
 | |
|   (let [[process (subprocess.Popen fullcmd
 | |
|                                    :stdout subprocess.PIPE
 | |
|                                    :stderr subprocess.PIPE
 | |
|                                    :shell True)]
 | |
|         [(, out err) (.communicate process)]]
 | |
|     (, out err process.returncode)))
 | |
| 
 | |
| (defn derive-max-code [code-pairs]
 | |
|   (reduce
 | |
|    (fn [m i] (if (> (abs (get i 0)) (abs m)) (get i 0) m))
 | |
|    code-pairs 0))
 | |
| 
 | |
| (defn derive-message-bodies [code-pairs]
 | |
|   (lmap (fn [i] (get i 1)) code-pairs))
 | |
| 
 | |
| (defn lmap (pred iter) (list (map pred iter)))
 | |
| 
 | |
| (defn encode-shell-messages [prefix messages]
 | |
|   (lmap (fn [line] (.format "{}{}" prefix (.decode line "utf-8")))
 | |
|         (.splitlines messages)))
 | |
| 
 | |
| (defn run-external-checker [filename check]
 | |
|   (let [[cmd (-> (get check "command")
 | |
|                  (.format
 | |
|                   :filename filename
 | |
|                   :config_path *config-path*))]
 | |
|         [(, out err returncode) (get-shell-response cmd)]]
 | |
|     (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 (+ (encode-shell-messages prefix out)
 | |
|                        (if err (encode-shell-messages prefix err) []))]]
 | |
|         [(or returncode 1) output])
 | |
|       [0 []])))
 | |
| 
 | |
| (defn matches-file [filename match-files]
 | |
|   (any (map (fn [match-file] (-> (.compile re match-file)
 | |
|                                  (.match filename)))
 | |
|             match-files)))
 | |
| 
 | |
| (defn check-scan-wanted [filename check]
 | |
|   (cond [(and (in "match_files" check)
 | |
|               (not (matches-file filename (get check "match_files")))) false]
 | |
|         [(and (in "ignore_files" check)
 | |
|               (matches-file filename (get check "ignore_files"))) false]
 | |
|         [true true]))
 | |
| 
 | |
| (defn check-files [filenames check]
 | |
|   (let [[filenames-to-check
 | |
|          (filter (fn [filename] (check-scan-wanted filename check)) filenames)]
 | |
|         [results-of-checks
 | |
|          (lmap (fn [filename]
 | |
|                  (run-external-checker filename check)) filenames-to-check)]
 | |
|         [messages (+ [(get check "output")] 
 | |
|                      (derive-message-bodies results-of-checks))]]
 | |
|     [(derive-max-code results-of-checks) messages]))
 | |
| 
 | |
| (defn gather-all-filenames []
 | |
|   (let [[build-filenames
 | |
|          (fn [filenames]
 | |
|            (map
 | |
|             (fn [f] (os.path.join (get filenames 0) f)) (get filenames 2)))]]
 | |
|     (list
 | |
|      (flatten
 | |
|       (list-comp (build-filenames o) [o (.walk os ".")])))))
 | |
| 
 | |
| (defn gather-staged-filenames [against]
 | |
|   (let [[(, out err returncode)
 | |
|          (get-git-response ["diff-index" "--name-status" against])]
 | |
|         [lines (.splitlines out)]
 | |
|         [matcher 
 | |
|          (fn [line] 
 | |
|            (.match *git-modified-pattern* (.decode line "utf-8")))]]
 | |
|     (list
 | |
|      (filter
 | |
|       (fn [x] (not (= x "")))
 | |
|       (list-comp (.group match "name") [match (map matcher lines)] match)))))
 | |
| 
 | |
| (defn run-checks-for [scan-all-files against]
 | |
|   (do
 | |
|    (run-git-command ["stash" "--keep-index"])
 | |
|    (let [[filenames-to-scan
 | |
|           (if scan-all-files
 | |
|             (gather-all-filenames)
 | |
|             (gather-staged-filenames against))]
 | |
|          [results-of-scan
 | |
|           (lmap (fn [check] (check-files filenames-to-scan check)) *checks*)]
 | |
|          [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))))
 | |
| 
 | |
| (defn get-head-tag []
 | |
|   (let [[empty-repository-hash "4b825dc642cb6eb9a060e54bf8d69288fbee4904"]
 | |
|         [(, out err returncode) (get-git-response ["rev-parse" "--verify HEAD"])]]
 | |
|     (if err empty-repository-hash "HEAD")))
 | |
| 
 | |
| (defmain [&rest args]
 | |
|   (let [[scan-all-files (and (> (len args) 1) (= (get args 2) "--all-files"))]]
 | |
|     (sys.exit (int (run-checks-for scan-all-files (get-head-tag))))))
 |