Show More
Commit Description:
Update README.
Commit Description:
Update README.
References:
File last commit:
Show/Diff file:
Action:
src/project_checkup/core.clj
157 lines | 5.9 KiB | text/x-clojure | ClojureLexer
157 lines | 5.9 KiB | text/x-clojure | ClojureLexer
r0 | #!/usr/bin/env lumo | |||
(ns project-checkup.core | ||||
(:gen-class) | ||||
(:require [clojure.java.shell :as shell] | ||||
r30 | [clojure.set :as set] | |||
r14 | [clojure.string :as string])) | |||
r0 | ||||
r14 | (defn get-extension [path] | |||
r8 | "Extracts the extension of a path. | |||
r32 | Returns the extension with the period, e.g., '.txt' because that's the | |||
format people are used to seeing extensions in." | ||||
r8 | (re-find #"\.[a-zA-Z0-9]+$" path)) | |||
r0 | ||||
r14 | (defn gather-untracked | |||
r10 | [vcs-systems] | |||
"Gather untracked files in Git or Mercurial." | ||||
r32 | (filter (comp not empty?) (reduce into [ | |||
r10 | (if (contains? vcs-systems ".hg") | |||
(string/split (:out (shell/sh "chg" "st" "-u" "-n")) #"\n")) | ||||
(if (contains? vcs-systems ".git") | ||||
r14 | (string/split (:out (shell/sh "git" "ls-files" "--others" | |||
r32 | "--exclude-standard")) #"\n"))]))) | |||
r0 | (defn gather-project-info | |||
r10 | "Creates a dictionary of project information." | |||
r0 | [] | |||
r14 | (let [all-files (map str (file-seq (clojure.java.io/file "."))) | |||
r10 | files (map #(clojure.string/replace % #"./(.*)" "$1") all-files ) | |||
r30 | vcs-systems (set/intersection #{".git" ".hg"} (set files))] | |||
r14 | {:files files | |||
r0 | :extensions (frequencies (map get-extension files )) | |||
r6 | :path (System/getProperty "user.dir") | |||
r14 | :untracked-files (gather-untracked vcs-systems) | |||
r29 | :readme (if-let [filename (some #{"README.md" "README.txt" "README.mkd"} files)] (slurp filename) "") }) ) | |||
r0 | ||||
r32 | ||||
(def doc-extensions ["" ".txt" ".mkd" ".md" ".rst"]) | ||||
(defn create-valid-names [exts names] | ||||
"Creates valid file names based on a sequence of exts and names." | ||||
(set (for [ext exts name names] | ||||
(str name ext)))) | ||||
r0 | (defn color [color string] | |||
(let [color-sequence (case color | ||||
:green "\u001B[32m" | ||||
:yellow "\u001B[33m" | ||||
:blue "\u001B[34m" | ||||
r4 | :red "\u001B[31m" | |||
:cyan "\u001B[36m" | ||||
r29 | :magenta "\u001B[35m") | |||
reset "\u001B[m"] | ||||
(str color-sequence string reset))) | ||||
r0 | ||||
(defn check-vcs [project] | ||||
(let [{files :files } project] | ||||
r29 | (boolean (some #{".git" ".hg"} files)))) | |||
r0 | ||||
(defn check-readme [project] | ||||
(let [{files :files } project] | ||||
r29 | (boolean (some #{"README.md" "README.txt" "README.mkd" "README"} files)))) | |||
r0 | ||||
r27 | (defn check-changelog [project] | |||
r32 | (let [{files :files } project | |||
changelog-names #{"CHANGELOG" "HISTORY" "NEWS" "RELEASES"}] | ||||
(boolean (some (create-valid-names doc-extensions changelog-names) files)))) | ||||
r27 | ||||
r0 | (defn check-untracked [project] | |||
(let [{untracked :untracked-files } project] | ||||
r29 | (= (count untracked) 0))) | |||
r0 | ||||
(defn check-taskpaper [project] | ||||
r4 | (let [{extensions :extensions files :files } project] | |||
r27 | (or (>= (get extensions ".taskpaper" 0) 1) | |||
r14 | (some #{"TODO" "TODO.txt" } files)))) | |||
r4 | ||||
(defn check-readme-placeholders [project] | ||||
r29 | (= (count (re-find #"(FIXME|TODO)" (:readme project))) 0)) | |||
r4 | ||||
r0 | ||||
r15 | (defn check-license [project] | |||
(let [{files :files } project] | ||||
r16 | (boolean (some #{"LICENSE" "LICENSE.txt" "LICENSE.md" "LICENSE.mkd"} files)))) | |||
r15 | ||||
r13 | (def checks [{:name "Project is checked into revision control" | |||
r0 | :description "" | |||
:function check-vcs | ||||
r14 | :level :error | |||
r29 | :follow-up "Initialize a repository."} | |||
r0 | {:name "Always True" | |||
:function #(or true %) | ||||
:level :error | ||||
r29 | :follow-up "This is a bug."} | |||
r13 | {:name "All files are tracked or ignored" | |||
r0 | :description "" | |||
:function check-untracked | ||||
:level :warning | ||||
r10 | :follow-up "Commit or ignore files from 'hg st -u' or 'git ls-files --others --exclude-standard'." } | |||
r13 | {:name "Project has a todo file" | |||
r0 | :function check-taskpaper | |||
:description "" | ||||
:level :suggestion | ||||
r29 | :follow-up "Add a todo file using Taskpaper."} | |||
r15 | {:name "Project has a README" | |||
r0 | :function check-readme | |||
:description "Readme exists" | ||||
r28 | :level :warning | |||
r29 | :follow-up "Add a README."} | |||
r27 | {:name "Project has a CHANGELOG" | |||
:function check-changelog | ||||
:description "Changelog exists" | ||||
r28 | :level :warning | |||
:follow-up "Add a CHANGELOG. Consider refering to keepachangelog.com for guidance." } | ||||
r4 | {:name "README has no placeholders" | |||
:function check-readme-placeholders | ||||
:description "No placeholders in README" | ||||
:level :error | ||||
r30 | :follow-up "Address placeholders or convert them to tasks."} | |||
r15 | { :name "Project has a license" | |||
:function check-license | ||||
:description "Project has a LICENSE file." | ||||
:level :warning ;going with warning because a project might not have a license before release. | ||||
r28 | :follow-up "Add a license to LICENSE. Consider using https://choosealicense.com/ for guidance." }]) | |||
r0 | ||||
r31 | (def false-colors {:suggestion :blue | |||
:warning :yellow | ||||
:error :red}) | ||||
(def level-prefixes {:suggestion "Suggested " | ||||
:warning "Recommended " | ||||
:error "Required "}) | ||||
r0 | (defn perform-check [check project] | |||
r29 | (let [{check-name :name :keys [function follow-up level]} check | |||
r0 | result (function project) | |||
r31 | false-color (level false-colors :red) | |||
prefix (level level-prefixes "Follow-up")] | ||||
r0 | {:name check-name | |||
:result result | ||||
r14 | :output (if result | |||
(color :green (str "✔" check-name "…passed!")) | ||||
r29 | (str (color false-color (str "❌" check-name "…failed!")) | |||
"\n\t" prefix " Follow up: " follow-up))})) | ||||
r0 | ||||
(defn -main | ||||
r4 | "Run checks." | |||
r0 | [& args] | |||
r14 | (try | |||
(let [project-info (gather-project-info)] | ||||
(doseq [check checks] | ||||
(println (:output (perform-check check project-info ))))) | ||||
r30 | (catch Exception ex ; Not expecting there to be a lot of exceptions, hence the catch-all. | |||
r14 | (.printStackTrace ex) | |||
(str "caught exception: " (.getMessage ex))) | ||||
r15 | (finally (shutdown-agents)))) | |||