1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Basic catalog generator for Nit packages
17 # See: <http://nitlanguage.org/catalog/>
19 # The tool scans packages and generates the HTML files of a catalog.
23 # * [X] scan packages and their `.ini`
24 # * [X] generate lists of packages
25 # * [X] generate a page per package with the readme and most metadata
26 # * [ ] link/include/be included in the documentation
27 # * [ ] propose `related packages`
28 # * [X] show directory content (a la nitls)
29 # * [X] gather git information from the working directory
30 # * [ ] gather git information from the repository
31 # * [ ] gather package information from github
32 # * [ ] gather people information from github
34 # * [X] separate information gathering from rendering
35 # * [ ] move up information gathering in (existing or new) service modules
36 # * [X] add command line options
37 # * [ ] harden HTML (escaping, path injection, etc)
38 # * [ ] nitcorn server with RESTful API
40 # ## Issues and limitations
42 # The tool works likee the other tools and expects to find valid Nit source code in the directories
44 # * cruft and temporary files will be collected
45 # * missing source file (e.g. not yet generated by nitcc) will make information
46 # incomplete (e.g. invalid module thus partial dependency and metrics)
48 # How to use the tool as the basis of a Nit code archive on the web usable with a package manager is not clear.
51 import md5
# To get gravatar images
52 import counter
# For statistics
53 import modelize
# To process and count classes and methods
56 # Return the associated metadata from the `ini`, if any
57 fun metadata
(key
: String): nullable String
60 if ini
== null then return null
64 # The consolidated list of tags
65 var tags
= new Array[String]
67 # The list of maintainers
68 var maintainers
= new Array[String]
70 # The list of contributors
71 var contributors
= new Array[String]
73 # The date of the most recent commit
74 var last_date
: nullable String = null
76 # The date of the oldest commit
77 var first_date
: nullable String = null
81 # Returns `log(self+1)`. Used to compute score of packages
82 fun score
: Float do return (self+1).to_f
.log
85 # The main class of the calatog generator that has the knowledge
89 # used to access the files and count source lines of code
90 var modelbuilder
: ModelBuilder
93 var tag2proj
= new MultiHashMap[String, MPackage]
95 # Packages by category
96 var cat2proj
= new MultiHashMap[String, MPackage]
98 # Packages by maintainer
99 var maint2proj
= new MultiHashMap[String, MPackage]
101 # Packages by contributors
102 var contrib2proj
= new MultiHashMap[String, MPackage]
104 # Dependency between packages
105 var deps
= new POSet[MPackage]
107 # Number of modules by package
108 var mmodules
= new Counter[MPackage]
110 # Number of classes by package
111 var mclasses
= new Counter[MPackage]
113 # Number of methods by package
114 var mmethods
= new Counter[MPackage]
116 # Number of line of code by package
117 var loc
= new Counter[MPackage]
119 # Number of commits by package
120 var commits
= new Counter[MPackage]
124 # The score is loosely computed using other metrics
125 var score
= new Counter[MPackage]
127 # Scan, register and add a contributor to a package
128 fun register_contrib
(person
: String, mpackage
: MPackage)
130 var projs
= contrib2proj
[person
]
131 if not projs
.has
(mpackage
) then projs
.add mpackage
134 # Compute information for a package
135 fun package_page
(mpackage
: MPackage)
137 var score
= score
[mpackage
].to_f
139 var mdoc
= mpackage
.mdoc_or_fallback
142 score
+= mdoc
.content
.length
.score
146 var tryit
= mpackage
.metadata
("upstream.tryit")
147 if tryit
!= null then
150 var apk
= mpackage
.metadata
("upstream.apk")
155 var homepage
= mpackage
.metadata
("upstream.homepage")
156 if homepage
!= null then
159 var maintainer
= mpackage
.metadata
("package.maintainer")
160 if maintainer
!= null then
162 register_contrib
(maintainer
, mpackage
)
163 mpackage
.maintainers
.add maintainer
164 var projs
= maint2proj
[maintainer
]
165 if not projs
.has
(mpackage
) then projs
.add mpackage
167 var license
= mpackage
.metadata
("package.license")
168 if license
!= null then
172 var browse
= mpackage
.metadata
("upstream.browse")
173 if browse
!= null then
177 var tags
= mpackage
.metadata
("package.tags")
178 var ts
= mpackage
.tags
180 for t
in tags
.split
(",") do
182 if t
== "" then continue
186 if ts
.is_empty
then ts
.add
"none"
187 if tryit
!= null then ts
.add
"tryit"
188 if apk
!= null then ts
.add
"apk"
190 tag2proj
[t
].add mpackage
193 cat2proj
[cat
].add mpackage
194 score
+= ts
.length
.score
196 if deps
.has
(mpackage
) then
197 score
+= deps
[mpackage
].greaters
.length
.score
198 score
+= deps
[mpackage
].direct_greaters
.length
.score
199 score
+= deps
[mpackage
].smallers
.length
.score
200 score
+= deps
[mpackage
].direct_smallers
.length
.score
203 var contributors
= mpackage
.contributors
204 var more_contributors
= mpackage
.metadata
("package.more_contributors")
205 if more_contributors
!= null then
206 for c
in more_contributors
.split
(",") do
207 contributors
.add c
.trim
210 if not contributors
.is_empty
then
211 for c
in contributors
do
212 register_contrib
(c
, mpackage
)
215 score
+= contributors
.length
.to_f
221 for g
in mpackage
.mgroups
do
222 mmodules
+= g
.mmodules
.length
223 for m
in g
.mmodules
do
224 var am
= modelbuilder
.mmodule2node
(m
)
226 var file
= am
.location
.file
228 loc
+= file
.line_starts
.length
- 1
231 for cd
in m
.mclassdefs
do
233 for pd
in cd
.mpropdefs
do
234 if not pd
isa MMethodDef then continue
240 self.mmodules
[mpackage
] = mmodules
241 self.mclasses
[mpackage
] = mclasses
242 self.mmethods
[mpackage
] = mmethods
243 self.loc
[mpackage
] = loc
245 #score += mmodules.score
246 score
+= mclasses
.score
247 score
+= mmethods
.score
250 self.score
[mpackage
] = score
.to_i
253 # Collect more information on a package using the `git` tool.
254 fun git_info
(mpackage
: MPackage)
256 var ini
= mpackage
.ini
257 if ini
== null then return
259 # TODO use real git info
260 #var repo = ini.get_or_null("upstream.git")
261 #var branch = ini.get_or_null("upstream.git.branch")
262 #var directory = ini.get_or_null("upstream.git.directory")
264 var dirpath
= mpackage
.root
.filepath
265 if dirpath
== null then return
267 # Collect commits info
268 var res
= git_run
("log", "--no-merges", "--follow", "--pretty=tformat:%ad;%aN <%aE>", "--", dirpath
)
269 var contributors
= new Counter[String]
270 var commits
= res
.split
("\n")
271 if commits
.not_empty
and commits
.last
== "" then commits
.pop
272 self.commits
[mpackage
] = commits
.length
274 var s
= l
.split_once_on
(';')
275 if s
.length
!= 2 or s
.last
== "" then continue
277 # Collect date of last and first commit
278 if mpackage
.last_date
== null then mpackage
.last_date
= s
.first
279 mpackage
.first_date
= s
.first
282 contributors
.inc
(s
.last
)
284 for c
in contributors
.sort
.reverse_iterator
do
285 mpackage
.contributors
.add c
291 # Execute a git command and return the result
292 fun git_run
(command
: String...): String
294 # print "git {command.join(" ")}"
295 var p
= new ProcessReader("git", command
...)