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
54 var nodes_counter
= new Counter[String]
56 # Count heading levels
57 var headings_counter
= new Counter[Int]
59 redef fun visit
(node
) do
60 nodes_counter
.inc node
.class_name
61 if node
isa MdHeading then
62 headings_counter
.inc node
.level
68 # All metrics about the readmes
70 super HashMap[MPackage, ReadmeMetric]
72 # Collect all metric names from submetrics
73 fun metrics_names
: ArraySet[String] do
74 var keys
= new ArraySet[String]
76 for mpackage
, values
in self do
77 keys
.add_all values
.keys
82 # Render `self` as a CsvDocument
83 fun to_csv
: CsvDocument do
84 var doc
= new CsvDocument
85 doc
.header
= metrics_names
.to_a
87 var metrics
= metrics_names
88 for mpackage
in self.keys
do
89 doc
.records
.add
self[mpackage
].to_csv_record
(metrics
)
94 # Print `self` into stdout
95 fun to_console
(toolcontext
: ToolContext) do
96 for mpackage
, values
in self do
97 if not values
.has_readme
then continue
98 values
.to_console
(toolcontext
)
102 # Collect metrics for all `mpackages`
103 fun collect_metrics
(mpackages
: Collection[MPackage]) do
104 for mpackage
in mpackages
do
105 var metric
= new ReadmeMetric(mpackage
)
106 metric
.collect_metrics
107 self[mpackage
] = metric
112 # Readme metrics associated to a Package
114 super HashMap[String, Int]
116 # Package this Readme is about
117 var mpackage
: MPackage
119 # Render `self` as a CsvDocument record
120 fun to_csv_record
(keys
: ArraySet[String]): Array[String] do
121 var record
= new Array[String]
122 record
.add mpackage
.full_name
124 if key
== keys
.first
then continue
125 var value
= if self.has_key
(key
) then self[key
] else 0
126 record
.add value
.to_s
131 # Return the value associated with `key` or `0`.
132 fun value_or_zero
(key
: String): Int do
133 return if self.has_key
(key
) then self[key
] else 0
136 # Print `self` on stdout
137 fun to_console
(toolcontext
: ToolContext) do
138 print toolcontext
.format_h2
("\n ## package {mpackage} ({readme_path or else "no readme"})")
139 for key
, value
in self do
140 print
" * {key} {value}"
144 # Collect metrics about `mpackage`
145 fun collect_metrics
do
146 if not has_package_dir
then
147 print
"Warning: no source file for `{mpackage}`"
148 self["has_package"] = 0
151 self["has_package"] = 1
153 if not has_readme
then
154 print
"Warning: no readme file for `{mpackage}`"
155 self["has_readme"] = 0
158 self["has_readme"] = 1
159 self["md_lines"] = md_lines
.length
161 var parser
= new MdParser
162 var node
= parser
.parse
(md_lines
.join
("\n"))
163 var v
= new MarkdownMetrics
165 for md_node
, value
in v
.nodes_counter
do
166 self[md_node
] = value
168 for level
, value
in v
.headings_counter
do
169 self["HL {level}"] = value
173 # Path to the package
174 var package_path
: nullable SourceFile is lazy
do return mpackage
.location
.file
176 # Is `mpackage` in its own directory?
177 var has_package_dir
: Bool is lazy
do
178 var path
= package_path
179 if path
== null then return false
180 return not path
.filename
.has_suffix
(".nit")
183 # Return the path to the `mpackage` Readme file
184 var readme_path
: nullable String is lazy
do
185 var package_path
= self.package_path
186 if package_path
== null then return null
187 return package_path
.filename
/ "README.md"
190 # Does `mpackage` has a Readme file?
191 var has_readme
: Bool is lazy
do
192 var readme_path
= self.readme_path
193 if readme_path
== null then return false
194 return readme_path
.to_s
.file_exists
197 # Read markdown lines
199 # Returns an empty array if the Readme does not exist.
200 var md_lines
: Array[String] is lazy
do
201 var path
= readme_path
202 if path
== null then return new Array[String]
203 return path
.to_path
.read_lines