]> gitweb.factorcode.org Git - factor.git/blob - extra/codebase-analyzer/codebase-analyzer.factor
ab5a80a42953ed21a3544ba6b7596a1d326e6058
[factor.git] / extra / codebase-analyzer / codebase-analyzer.factor
1 ! Copyright (C) 2022 Doug Coleman.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors alien.c-types assocs assocs.extras combinators
4 combinators.short-circuit formatting io io.backend
5 io.directories io.encodings.binary io.files io.files.info
6 io.files.types io.pathnames kernel math math.statistics
7 prettyprint sequences sets sorting specialized-arrays
8 tools.memory.private tools.wc unicode ;
9 IN: codebase-analyzer
10
11 : file-sizes ( paths -- assoc )
12     [ dup file-info size>> ] { } map>assoc ;
13
14 : binary-file? ( path -- ? )
15     binary [ 1024 read ] with-file-reader [ 0 = ] any? ;
16
17 : binary-files ( paths -- ? ) [ binary-file? ] filter ;
18
19 : partition-binary ( paths -- binary text )
20     [ binary-file? ] partition ;
21
22 : with-file-extensions ( paths -- paths' )
23     [ has-file-extension? ] filter ;
24
25 : without-git-paths ( paths -- paths' )
26     [ "/.git/" subseq-index? ] reject ;
27
28 : without-node-modules-paths ( paths -- paths' )
29     [ "/node_modules/" subseq-index? ] reject ;
30
31 : regular-directory-files ( path -- seq )
32     recursive-directory-files
33     [ link-info type>> +regular-file+ = ] filter ;
34
35 : codebase-paths ( path -- seq )
36     regular-directory-files
37     without-git-paths ;
38
39 : count-by-file-extension ( paths -- assoc )
40     with-file-extensions
41     [ file-extension ] histogram-by
42     sort-values ;
43
44 : collect-extensions-by-line-count ( paths -- assoc )
45     with-file-extensions
46     [ wc ] collect-by
47     sort-values ;
48
49 : collect-by-file-extension ( paths -- assoc )
50     with-file-extensions
51     [ file-extension ] collect-by ;
52
53 : sum-line-counts-by-extension ( paths -- assoc )
54     [ binary-file? ] reject
55     collect-by-file-extension
56     [ [ wc ] map-sum ] assoc-map
57     sort-values ;
58
59 : sum-sizes-by-extension ( paths -- assoc )
60     collect-by-file-extension
61     [ [ file-info size>> ] map-sum ] assoc-map
62     sort-values ;
63
64
65 : cmake-file? ( path -- ? ) { [ "CMakeLists.txt" tail? ] [ ".cmake" tail? ] } 1|| ;
66 : cmake-files ( paths -- paths' ) [ cmake-file? ] filter ;
67 : uses-cmake? ( paths -- ? ) [ cmake-file? ] any? ;
68
69 : shell-file? ( path -- ? ) >lower file-extension { "sh" "zsh" } member? ;
70 : shell-files ( paths -- paths' ) [ shell-file? ] filter ;
71 : uses-shell? ( paths -- ? ) [ shell-file? ] any? ;
72
73 : swift-files ( paths -- paths' ) [ ".swift" tail? ] filter ;
74
75 : c-file? ( path -- ? )
76     >lower file-extension { "h" "c" } member? ;
77 : c-files ( paths -- paths' ) [ c-file? ] filter ;
78
79 : cpp-file? ( path -- ? )
80     >lower file-extension { "h" "hh" "hpp" "cc" "cpp" } member? ;
81 : cpp-files ( paths -- paths' ) [ cpp-file? ] filter ;
82
83 : python-file? ( path -- ? )
84     >lower file-extension {
85         "py" "py3" "pyc" "pyo" "pyw" "pyx" "pyd"
86         "pxd" "pxi" "pyd" "pxi" "pyi" "pyz" "pwxz" "pth"
87     } member? ;
88 : python-files ( paths -- paths' ) [ python-file? ] filter ;
89
90 : markdown-file? ( path -- ? ) { [ ".md" tail? ] [ ".markdown" tail? ] } 1|| ;
91 : markdown-files ( paths -- paths' ) [ markdown-file? ] filter ;
92
93 : txt-file? ( path -- ? )
94     {
95         [ { [ ".txt" tail? ] [ ".TXT" tail? ] } 1|| ]
96         [ "CMakeLists.txt" tail? not ]
97     } 1&& ;
98 : txt-files ( paths -- paths' ) [ txt-file? ] filter ;
99
100 : license-file? ( path -- ? )
101     >lower { [ file-stem "license" = ] [ "license-mit" tail? ] } 1|| ;
102
103 : license-files ( paths -- paths' ) [ license-file? ] filter ;
104
105 : json-file? ( path -- ? )
106     >lower file-extension
107     { "json" "jsonc" } member? ;
108
109 : json-files ( paths -- paths' ) [ json-file? ] filter ;
110
111 : yaml-file? ( path -- ? ) { [ ".yaml" tail? ] [ ".yml" tail? ] } 1|| ;
112 : yaml-files ( paths -- paths' ) [ yaml-file? ] filter ;
113 : uses-yaml? ( paths -- ? ) [ yaml-file? ] any? ;
114
115 : docker-file? ( path -- ? ) >lower file-name { "dockerfile" ".dockerignore" "docker-compose.yaml" } member? ;
116 : docker-files ( paths -- paths' ) [ docker-file? ] filter ;
117 : uses-docker? ( paths -- ? ) [ docker-file? ] any? ;
118
119 : make-file? ( path -- ? ) >lower file-name { "gnumakefile" "makefile" "nmakefile" } member? ;
120 : make-files ( paths -- paths' ) [ make-file? ] filter ;
121 : uses-make? ( paths -- ? ) [ make-file? ] any? ;
122
123 : web-file? ( path -- ? )
124     >lower file-extension
125     {
126         "css" "scss" "js" "jsx" "ejs" "mjs" "ts" "tsx" "json" "html"
127         "less" "mustache" "snap" "wasm"
128     } member? ;
129 : web-files ( paths -- paths' ) [ web-file? ] filter ;
130
131 : rc-file? ( path -- ? ) >lower file-name { [ "." head? ] [ "rc" tail? ] } 1&& ;
132 : rc-files ( paths -- paths' ) [ rc-file? ] filter ;
133
134 : env-file? ( path -- ? ) >lower ".env" tail? ;
135 : env-files ( paths -- paths' ) [ env-file? ] filter ;
136
137 : image-file? ( path -- ? ) >lower file-extension { "png" "jpg" "jpeg" "ico" } member? ;
138 : image-files ( paths -- paths' ) [ image-file? ] filter ;
139
140 : ignore-file? ( path -- ? ) >lower file-name { [ "." head? ] [ "ignore" tail? ] } 1&& ;
141 : ignore-files ( paths -- paths' ) [ ignore-file? ] filter ;
142
143 : has-package-json? ( path -- ? ) "package.json" append-path file-exists? ;
144 : uses-git? ( path -- ? ) ".git" append-path file-exists? ;
145
146 : diff-paths ( paths quot -- paths' )
147     keep swap [ [ normalize-path ] map ] bi@ diff ; inline
148
149 : assoc. ( assoc -- )
150     [ commas ] map-values simple-table. ;
151
152 : analyze-codebase-path ( path -- )
153     {
154         [ normalize-path "project at path `%s`" sprintf print nl ]
155         [ uses-git? [ "uses git" print ] when ]
156         [ has-package-json? [ "has a package.json file" print ] when ]
157     } cleave ;
158
159 : analyze-codebase-paths ( paths -- )
160     {
161         [
162             partition-binary
163             [ length "%d binary files" sprintf print ]
164             [ length "%d text files" sprintf print ] bi*
165         ]
166         [ uses-cmake? [ "uses cmake" print ] when ]
167         [ uses-make? [ "uses make" print ] when ]
168         [ rc-files [ length "has %d rc files" sprintf print ] unless-empty ]
169         [ ignore-files [ length "has %d ignore files" sprintf print ] unless-empty nl ]
170         [ "Top 20 largest files" print file-sizes sort-values 20 index-or-length tail* [ normalize-path ] map-keys reverse assoc. nl ]
171         [ "Top 10 file extension sizes" print sum-sizes-by-extension 10 index-or-length tail* reverse assoc. nl ]
172         [ "Top 10 text file line counts" print sum-line-counts-by-extension 10 index-or-length tail* reverse assoc. nl ]
173         [ "Top 10 file extension counts" print count-by-file-extension 10 index-or-length tail* reverse assoc. nl ]
174     } cleave ;
175
176 : analyze-codebase ( path -- )
177     [ analyze-codebase-path ]
178     [ codebase-paths analyze-codebase-paths ] bi ;
179
180 : analyze-codebases ( path -- )
181     [ directory-files ] keep [ prepend-path ] curry map
182     [ analyze-codebase ] each ;