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 # Collect common metrics about README files
17 # Also works with generic Markdown files.
21 import model
::model_collect
24 redef class ToolContext
26 # README related metrics phase
27 var readme_metrics_phase
: Phase = new ReadmeMetricsPhase(self, null)
30 # Extract metrics about README files
31 private class ReadmeMetricsPhase
34 redef fun process_mainmodule
(mainmodule
, given_mmodules
) do
35 if not toolcontext
.opt_readme
.value
and not toolcontext
.opt_all
.value
then return
37 print toolcontext
.format_h1
("\n# ReadMe metrics")
38 var model
= toolcontext
.modelbuilder
.model
40 var metrics
= new ReadmeMetrics
41 metrics
.collect_metrics
(model
.mpackages
)
42 metrics
.to_console
(toolcontext
)
44 var csv
= toolcontext
.opt_csv
.value
45 if csv
then metrics
.to_csv
.write_to_file
("{toolcontext.opt_dir.value or else "metrics"}/readme.csv")
49 # A Markdown decorator that collects metrics about a Readme content
50 class MetricsDecorator
54 var block_counter
= new Counter[String]
57 var headline_counter
= new Counter[Int]
59 redef fun add_ruler
(v
, block
) do
60 block_counter
.inc block
.class_name
64 redef fun add_headline
(v
, block
) do
65 block_counter
.inc block
.class_name
66 headline_counter
.inc block
.depth
70 redef fun add_paragraph
(v
, block
) do
71 block_counter
.inc block
.class_name
75 redef fun add_code
(v
, block
) do
76 block_counter
.inc block
.class_name
80 redef fun add_blockquote
(v
, block
) do
81 block_counter
.inc block
.class_name
85 redef fun add_unorderedlist
(v
, block
) do
86 block_counter
.inc block
.class_name
90 redef fun add_orderedlist
(v
, block
) do
91 block_counter
.inc block
.class_name
95 redef fun add_listitem
(v
, block
) do
96 block_counter
.inc block
.class_name
100 redef fun add_image
(v
, link
, name
, comment
) do
101 block_counter
.inc
"Image"
105 redef fun add_link
(v
, link
, name
, comment
) do
106 block_counter
.inc
"Link"
110 redef fun add_span_code
(v
, text
, from
, to
) do
111 block_counter
.inc
"SpanCode"
116 # All metrics about the readmes
118 super HashMap[MPackage, ReadmeMetric]
120 # Collect all metric names from submetrics
121 fun metrics_names
: ArraySet[String] do
122 var keys
= new ArraySet[String]
124 for mpackage
, values
in self do
125 keys
.add_all values
.keys
130 # Render `self` as a CsvDocument
131 fun to_csv
: CsvDocument do
132 var doc
= new CsvDocument
133 doc
.header
= metrics_names
.to_a
135 var metrics
= metrics_names
136 for mpackage
in self.keys
do
137 doc
.records
.add
self[mpackage
].to_csv_record
(metrics
)
142 # Print `self` into stdout
143 fun to_console
(toolcontext
: ToolContext) do
144 for mpackage
, values
in self do
145 if not values
.has_readme
then continue
146 values
.to_console
(toolcontext
)
150 # Collect metrics for all `mpackages`
151 fun collect_metrics
(mpackages
: Collection[MPackage]) do
152 for mpackage
in mpackages
do
153 var metric
= new ReadmeMetric(mpackage
)
154 metric
.collect_metrics
155 self[mpackage
] = metric
160 # Readme metrics associated to a Package
162 super HashMap[String, Int]
164 # Package this Readme is about
165 var mpackage
: MPackage
167 # Render `self` as a CsvDocument record
168 fun to_csv_record
(keys
: ArraySet[String]): Array[String] do
169 var record
= new Array[String]
170 record
.add mpackage
.full_name
172 if key
== keys
.first
then continue
173 var value
= if self.has_key
(key
) then self[key
] else 0
174 record
.add value
.to_s
179 # Return the value associated with `key` or `0`.
180 fun value_or_zero
(key
: String): Int do
181 return if self.has_key
(key
) then self[key
] else 0
184 # Print `self` on stdout
185 fun to_console
(toolcontext
: ToolContext) do
186 print toolcontext
.format_h2
("\n ## package {mpackage} ({readme_path or else "no readme"})")
187 for key
, value
in self do
188 print
" * {key} {value}"
192 # Collect metrics about `mpackage`
193 fun collect_metrics
do
194 if not has_package_dir
then
195 print
"Warning: no source file for `{mpackage}`"
196 self["has_package"] = 0
199 self["has_package"] = 1
201 if not has_readme
then
202 print
"Warning: no readme file for `{mpackage}`"
203 self["has_readme"] = 0
206 self["has_readme"] = 1
207 self["md_lines"] = md_lines
.length
209 collect_sections_metrics
210 collect_blocs_metrics
213 # Path to the package
214 var package_path
: nullable SourceFile is lazy
do return mpackage
.location
.file
216 # Is `mpackage` in its own directory?
217 var has_package_dir
: Bool is lazy
do
218 var path
= package_path
219 if path
== null then return false
220 return not path
.filename
.has_suffix
(".nit")
223 # Return the path to the `mpackage` Readme file
224 var readme_path
: nullable String is lazy
do
225 var package_path
= self.package_path
226 if package_path
== null then return null
227 return package_path
.filename
/ "README.md"
230 # Does `mpackage` has a Readme file?
231 var has_readme
: Bool is lazy
do
232 var readme_path
= self.readme_path
233 if readme_path
== null then return false
234 return readme_path
.to_s
.file_exists
237 # Read markdown lines
239 # Returns an empty array if the Readme does not exist.
240 var md_lines
: Array[String] is lazy
do
241 var path
= readme_path
242 if path
== null then return new Array[String]
243 return path
.to_path
.read_lines
246 # Markdown decorator used to visit Markdown content
247 var md_decorator
: MetricsDecorator is lazy
do
248 var md_decorator
= new MetricsDecorator
249 var md_proc
= new MarkdownProcessor
250 md_proc
.decorator
= md_decorator
251 md_proc
.process
(md_lines
.join
("\n"))
255 # Collect metrics related to section headings
256 fun collect_sections_metrics
do
257 self["nb_section"] = md_decorator
.headline_counter
.sum
258 for lvl
, count
in md_decorator
.headline_counter
do
259 self["HL {lvl}"] = count
263 # Collect metrics related to Markdown blocks
264 fun collect_blocs_metrics
do
265 self["md_blocks"] = md_decorator
.block_counter
.sum
266 for block
, count
in md_decorator
.block_counter
do