Created
May 14, 2011 23:14
-
-
Save davidandrzej/972741 to your computer and use it in GitHub Desktop.
Count project lines of code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Calculate lines of code contained in a given directory | |
; -ignores empty lines | |
; -ignores comment-only lines | |
; -does *not* ignore block comments | |
; | |
; David Andrzejewski ([email protected]) | |
; | |
; Command-line arguments | |
; 0 Code root directory | |
; | |
; EX: clj loc.clj ~/private/myProject | |
; | |
(use '[clojure.contrib.duck-streams :only (read-lines)]) | |
(import java.io.File) | |
; Recursively calculated line-of-code counts at a given file/directory | |
(defstruct code-count :path :counts :children) | |
; A type of source file | |
(defstruct src-type :name :extension :commentchar) | |
; Pre-defined source file types | |
(def clojure (struct src-type :clojure "clj" ";")) | |
(def python (struct src-type :python "py" "#")) | |
(def csrc (struct src-type :csrc "c" "//")) | |
(def cpp (struct src-type :cpp "cpp" "//")) | |
(def cheader (struct src-type :cheader "h" "//")) | |
(def srctypes (list clojure python csrc cheader cpp)) | |
; Pre-defined 'ignore' patterns | |
(def svn (list ".*\\.svn$")) | |
(def hg (list ".*\\.hg$")) | |
(def build (list ".*build$")) | |
(def ignores (concat svn hg build)) | |
(defn not-nil? | |
"Could not find this in core API...?!" | |
[val] | |
(not (nil? val))) | |
(defn has-extension | |
"Does this filename have this extension?" | |
[extension fname] | |
(not-nil? (re-matches (re-pattern (str ".+\\." extension "$")) fname))) | |
(defn valid-line | |
"Is this line 1) non-whitespace? and 2) not a comment?" | |
[commentchar line] | |
(and (not (zero? (.. line trim length))) | |
(nil? (re-matches (re-pattern (str "^\\s*" commentchar ".*")) | |
line)))) | |
(defn lines-of-code | |
"Count non-commented, non-empty lines of code in this file" | |
[commentchar fname] | |
(count (filter (partial valid-line commentchar) (read-lines fname)))) | |
(defn process-dir | |
"Takes a File object" | |
[dir getrelpath fcounter ignorer] | |
(struct code-count (getrelpath dir) nil | |
(for [file (filter ignorer (. dir listFiles))] | |
(if (. file isDirectory) | |
(process-dir file getrelpath fcounter ignorer) | |
(struct code-count (getrelpath file) | |
(fcounter file) | |
nil))))) | |
(defn get-relative-path | |
"Get path relative to some base path" | |
[basepath file] | |
(. (. file getCanonicalPath) substring (. basepath length))) | |
(defn path-depth | |
"How deep is this path? (for indenting)" | |
[path] | |
(dec (count (filter | |
#(. (new Character java.io.File/separatorChar) equals %1) | |
(. path toCharArray))))) | |
(defn get-counts | |
"Code counts at this entry (include subdirectories)" | |
[ccount] | |
(apply merge-with + (cons (:counts ccount) | |
(map get-counts (:children ccount))))) | |
(defn indent-depth | |
"Add tabs for path depth" | |
[pdepth] | |
(apply str (for [i (range pdepth)] "\t"))) | |
(defn nonempty-count? | |
"Don't print non-matching files or empty directories" | |
[ccount] | |
(or (not-nil? (get-counts ccount)) (not-nil? (:children ccount)))) | |
(defn print-indented-counts | |
"Print source type LOC counts, appropriately indented" | |
[indent cts] | |
(doseq [key (keys cts)] | |
(println (format " %s%s %d" indent key (key cts))))) | |
(defn print-counts | |
"Recursively print LOC counts for all entries" | |
[ccount] | |
(let [cts (get-counts ccount) | |
indent (indent-depth (path-depth (:path ccount)))] | |
(println (str indent (:path ccount))) | |
(print-indented-counts indent cts) | |
(doseq [child (filter nonempty-count? (:children ccount))] | |
(print-counts child)))) | |
(defn count-loc | |
"Count lines of code for a particular type of src" | |
[srctype fpath] | |
(if (has-extension (:extension srctype) fpath) | |
(hash-map (:name srctype) | |
(lines-of-code (:commentchar srctype) fpath)))) | |
(defn count-file | |
"Try to count srctypes code for this file" | |
[srctypes file] | |
(let [fpath (. file getCanonicalPath)] | |
(apply merge-with + (map #(count-loc %1 fpath) srctypes)))) | |
(defn not-ignore | |
"True if we should *not* ignore" | |
[ignores file] | |
(every? nil? (map #(re-matches (re-pattern %1) | |
(. file getCanonicalPath)) ignores))) | |
(let [rootdirname (nth *command-line-args* 0) | |
rootdir (new java.io.File rootdirname) | |
basepath (. rootdir getCanonicalPath)] | |
(do | |
(println basepath) | |
(print-counts (process-dir rootdir | |
(partial get-relative-path basepath) | |
(partial count-file srctypes) | |
(partial not-ignore ignores))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment