countless 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.editorconfig +30 -0
- data/.github/workflows/documentation.yml +39 -0
- data/.github/workflows/test.yml +69 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.rubocop.yml +58 -0
- data/.simplecov +5 -0
- data/.yardopts +6 -0
- data/Appraisals +25 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Guardfile +44 -0
- data/LICENSE +21 -0
- data/Makefile +151 -0
- data/README.md +282 -0
- data/Rakefile +27 -0
- data/bin/cloc +17236 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/countless.gemspec +44 -0
- data/doc/assets/project.svg +68 -0
- data/docker-compose.yml +8 -0
- data/gemfiles/rails_5.2.gemfile +9 -0
- data/gemfiles/rails_6.0.gemfile +9 -0
- data/gemfiles/rails_6.1.gemfile +9 -0
- data/gemfiles/rails_7.0.gemfile +9 -0
- data/lib/countless/annotations.rb +243 -0
- data/lib/countless/cloc.rb +67 -0
- data/lib/countless/configuration.rb +209 -0
- data/lib/countless/extensions/configuration_handling.rb +83 -0
- data/lib/countless/rake_tasks.rake +31 -0
- data/lib/countless/rake_tasks.rb +6 -0
- data/lib/countless/statistics.rb +320 -0
- data/lib/countless/version.rb +23 -0
- data/lib/countless.rb +35 -0
- metadata +282 -0
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'countless'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/countless.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'countless/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'countless'
|
9
|
+
spec.version = Countless::VERSION
|
10
|
+
spec.authors = ['Hermann Mayer']
|
11
|
+
spec.email = ['hermann.mayer@hausgold.de']
|
12
|
+
|
13
|
+
spec.summary = 'Code statistics/annotations helpers'
|
14
|
+
spec.description = 'This gem includes reusable code statistics / ' \
|
15
|
+
'annotations helpers / Rake tasks.'
|
16
|
+
|
17
|
+
spec.homepage = 'https://github.com/hausgold/countless'
|
18
|
+
spec.license = 'MIT'
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
21
|
+
f.match(%r{^(test|spec|features)/})
|
22
|
+
end + ['bin/cloc']
|
23
|
+
spec.bindir = 'exe'
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ['lib']
|
26
|
+
|
27
|
+
spec.required_ruby_version = '>= 2.5'
|
28
|
+
|
29
|
+
spec.add_runtime_dependency 'activesupport', '>= 5.2.0'
|
30
|
+
spec.add_runtime_dependency 'zeitwerk', '~> 2.4'
|
31
|
+
|
32
|
+
spec.add_development_dependency 'appraisal'
|
33
|
+
spec.add_development_dependency 'benchmark-ips', '~> 2.10'
|
34
|
+
spec.add_development_dependency 'bundler', '>= 1.16', '< 3'
|
35
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.7'
|
36
|
+
spec.add_development_dependency 'irb', '~> 1.2'
|
37
|
+
spec.add_development_dependency 'rspec', '~> 3.9'
|
38
|
+
spec.add_development_dependency 'rubocop', '~> 1.25'
|
39
|
+
spec.add_development_dependency 'rubocop-rails', '~> 2.14'
|
40
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.10'
|
41
|
+
spec.add_development_dependency 'simplecov', '< 0.18'
|
42
|
+
spec.add_development_dependency 'yard', '~> 0.9.18'
|
43
|
+
spec.add_development_dependency 'yard-activesupport-concern', '~> 0.0.1'
|
44
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<svg
|
3
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
4
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
5
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
6
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
8
|
+
version="1.1"
|
9
|
+
id="Ebene_1"
|
10
|
+
x="0px"
|
11
|
+
y="0px"
|
12
|
+
viewBox="0 0 800 200"
|
13
|
+
xml:space="preserve"
|
14
|
+
width="800"
|
15
|
+
height="200"><metadata
|
16
|
+
id="metadata33"><rdf:RDF><cc:Work
|
17
|
+
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
18
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
19
|
+
id="defs31" />
|
20
|
+
<style
|
21
|
+
type="text/css"
|
22
|
+
id="style2">
|
23
|
+
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E73E11;}
|
24
|
+
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0371B9;}
|
25
|
+
.st2{fill:#132E48;}
|
26
|
+
.st3{font-family:'OpenSans-Bold';}
|
27
|
+
.st4{font-size:29.5168px;}
|
28
|
+
.st5{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
|
29
|
+
.st6{opacity:0.5;fill:#132E48;}
|
30
|
+
.st7{font-family:'OpenSans';}
|
31
|
+
.st8{font-size:12px;}
|
32
|
+
</style>
|
33
|
+
<g
|
34
|
+
transform="translate(0,1.53584)"
|
35
|
+
id="g828"><g
|
36
|
+
transform="translate(35.93985,35.66416)"
|
37
|
+
id="g8">
|
38
|
+
<path
|
39
|
+
style="clip-rule:evenodd;fill:#e73e11;fill-rule:evenodd"
|
40
|
+
id="path4"
|
41
|
+
d="m -0.1,124.4 c 0,0 33.7,-123.2 66.7,-123.2 12.8,0 26.9,21.9 38.8,47.2 -23.6,27.9 -66.6,59.7 -94,76 -7.1,0 -11.5,0 -11.5,0 z"
|
42
|
+
class="st0" />
|
43
|
+
<path
|
44
|
+
style="clip-rule:evenodd;fill:#0371b9;fill-rule:evenodd"
|
45
|
+
id="path6"
|
46
|
+
d="m 88.1,101.8 c 13.5,-10.4 18.4,-16.2 27.1,-25.4 10,25.7 16.7,48 16.7,48 0,0 -41.4,0 -78,0 14.6,-7.9 18.7,-10.7 34.2,-22.6 z"
|
47
|
+
class="st1" />
|
48
|
+
</g><text
|
49
|
+
y="106.40316"
|
50
|
+
x="192.43155"
|
51
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.51733398px;font-family:'Open Sans', sans-serif;-inkscape-font-specification:'OpenSans-Bold, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#132e48"
|
52
|
+
id="text10"
|
53
|
+
class="st2 st3 st4">Countless</text>
|
54
|
+
<rect
|
55
|
+
style="clip-rule:evenodd;fill:none;fill-rule:evenodd"
|
56
|
+
id="rect12"
|
57
|
+
height="24"
|
58
|
+
width="314.5"
|
59
|
+
class="st5"
|
60
|
+
y="118.06416"
|
61
|
+
x="194.23985" /><text
|
62
|
+
y="127.22146"
|
63
|
+
x="194.21715"
|
64
|
+
style="font-size:12px;font-family:'Open Sans', sans-serif;opacity:0.5;fill:#132e48;-inkscape-font-specification:'Open Sans, sans-serif, Normal';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;"
|
65
|
+
id="text14"
|
66
|
+
class="st6 st7 st8">Code statistics/annotations helpers</text>
|
67
|
+
</g>
|
68
|
+
</svg>
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Countless
|
4
|
+
# Annotation objects are triplets +:line+, +:tag+, +:text+ that represent the
|
5
|
+
# line where the annotation lives, its tag, and its text. Note the filename
|
6
|
+
# is not stored.
|
7
|
+
#
|
8
|
+
# Annotations are looked for in comments and modulus whitespace they have to
|
9
|
+
# start with the tag optionally followed by a colon. Everything up to the end
|
10
|
+
# of the line (or closing ERB comment tag) is considered to be their text.
|
11
|
+
#
|
12
|
+
# Heavily stolen from: https://bit.ly/3nBS0aj
|
13
|
+
#
|
14
|
+
# rubocop:disable Metrics/ClassLength because of the nested Annotation class
|
15
|
+
class Annotations
|
16
|
+
attr_reader :tag, :options, :dirs, :files, :annotations
|
17
|
+
|
18
|
+
# Setup a new instance of the source annotation extractor.
|
19
|
+
#
|
20
|
+
# If +tag+ is +nil+, annotations with either default or registered tags are
|
21
|
+
# printed. Specific directories can be explicitly set using the +:dirs+
|
22
|
+
# key in +options+.
|
23
|
+
#
|
24
|
+
# Countless::SourceAnnotationExtractor.enumerate(
|
25
|
+
# 'TODO|FIXME', dirs: %w(app lib), tag: true
|
26
|
+
# )
|
27
|
+
#
|
28
|
+
# If +options+ has a +:tag+ flag, it will be passed to each annotation's
|
29
|
+
# +to_s+. See +#find_in+ for a list of file extensions that will be taken
|
30
|
+
# into account.
|
31
|
+
#
|
32
|
+
# @param tag [String, nil] the annotation tags to use
|
33
|
+
# @param options [Hash{Symbol => Mixed}] additional options
|
34
|
+
# @return [Countless::SourceAnnotationExtractor] the new instance
|
35
|
+
def initialize(tag = nil, options = {})
|
36
|
+
@tag = tag || Annotation.tags.join('|')
|
37
|
+
@dirs = options.delete(:dirs) || Annotation.directories
|
38
|
+
@files = options.delete(:files) || Annotation.files
|
39
|
+
@options = options
|
40
|
+
@annotations = find(dirs: dirs, files: files)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a hash that maps filenames under +dirs+ (recursively) to arrays
|
44
|
+
# with their annotations.
|
45
|
+
#
|
46
|
+
# @param files [Array<String>] the files to use
|
47
|
+
# @param dirs [Array<String>] the directories to use
|
48
|
+
# @return [Hash{String => Array<Annotation>}] the found annotations per file
|
49
|
+
def find(files: [], dirs: [])
|
50
|
+
results = {}
|
51
|
+
files.inject(results) { |memo, file| memo.update(annotations_in(file)) }
|
52
|
+
dirs.inject(results) { |memo, dir| memo.update(find_in(dir)) }
|
53
|
+
results
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a hash that maps filenames under +dir+ (recursively) to arrays
|
57
|
+
# with their annotations. Files with extensions registered in
|
58
|
+
# +Countless::SourceAnnotationExtractor::Annotation.extensions+ are
|
59
|
+
# taken into account. Only files with annotations are included.
|
60
|
+
#
|
61
|
+
# @param dir [String] the directory to use
|
62
|
+
# @return [Hash{String => Array<Annotation>}] the found annotations per file
|
63
|
+
def find_in(dir)
|
64
|
+
results = {}
|
65
|
+
|
66
|
+
Dir.glob("#{dir}/*") do |item|
|
67
|
+
next if File.basename(item)[0] == '.'
|
68
|
+
|
69
|
+
if File.directory?(item)
|
70
|
+
results.update(find_in(item))
|
71
|
+
else
|
72
|
+
results.update(annotations_in(item))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
results
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a hash that maps filenames under +file+ (de-glob-bed) to arrays
|
80
|
+
# with their annotations. Files with extensions registered in
|
81
|
+
# +Countless::SourceAnnotationExtractor::Annotation.extensions+ are
|
82
|
+
# taken into account. Only files with annotations are included.
|
83
|
+
#
|
84
|
+
# @param file [String] the file to use
|
85
|
+
# @return [Hash{String => Array<Annotation>}] the found annotations per file
|
86
|
+
def annotations_in(file)
|
87
|
+
results = {}
|
88
|
+
|
89
|
+
Dir.glob(file) do |item|
|
90
|
+
extension = \
|
91
|
+
Annotation.extensions.detect { |regexp, _block| regexp.match(item) }
|
92
|
+
|
93
|
+
if extension
|
94
|
+
pattern = extension.last.call(tag)
|
95
|
+
results.update(extract_annotations_from(item, pattern)) if pattern
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
results
|
100
|
+
end
|
101
|
+
|
102
|
+
# If +file+ is the filename of a file that contains annotations this method
|
103
|
+
# returns a hash with a single entry that maps +file+ to an array of its
|
104
|
+
# annotations. Otherwise it returns an empty hash.
|
105
|
+
#
|
106
|
+
# @param file [String] the file path to extract annotations from
|
107
|
+
# @param pattern [RegExp] the matching pattern to use
|
108
|
+
# @return [Hash{String => Annotation}] the found annotation of the file
|
109
|
+
def extract_annotations_from(file, pattern)
|
110
|
+
lineno = 0
|
111
|
+
result = File.readlines(
|
112
|
+
file, encoding: Encoding::BINARY
|
113
|
+
).inject([]) do |list, line|
|
114
|
+
lineno += 1
|
115
|
+
next list unless line =~ pattern
|
116
|
+
|
117
|
+
list << Annotation.new(lineno, Regexp.last_match(1),
|
118
|
+
Regexp.last_match(2))
|
119
|
+
end
|
120
|
+
result.empty? ? {} : { file => result }
|
121
|
+
end
|
122
|
+
|
123
|
+
# Formats the found annotations.
|
124
|
+
#
|
125
|
+
# @return [String] the formatted annotations
|
126
|
+
#
|
127
|
+
# rubocop:disable Metrics/AbcSize because of the indentation logic
|
128
|
+
def to_s
|
129
|
+
buf = []
|
130
|
+
options[:indent] = annotations.flat_map do |_f, a|
|
131
|
+
a.map(&:line)
|
132
|
+
end.max.to_s.size
|
133
|
+
annotations.keys.sort.each do |file|
|
134
|
+
buf << "#{file}:"
|
135
|
+
annotations[file].each { |note| buf << " * #{note.to_s(options)}" }
|
136
|
+
buf << ''
|
137
|
+
end
|
138
|
+
buf.join("\n")
|
139
|
+
end
|
140
|
+
# rubocop:enable Metrics/AbcSize
|
141
|
+
|
142
|
+
# A single annotation representation.
|
143
|
+
Annotation = Struct.new(:line, :tag, :text) do
|
144
|
+
# Returns the currently configured files.
|
145
|
+
#
|
146
|
+
# @return [Array<String>] the configured files
|
147
|
+
def self.files
|
148
|
+
@files ||= \
|
149
|
+
Countless.configuration.annotations_files.deep_dup
|
150
|
+
end
|
151
|
+
|
152
|
+
# Registers additional files to be included.
|
153
|
+
#
|
154
|
+
# @param dirs [Array<String>] the additional files to include
|
155
|
+
def self.register_files(*dirs)
|
156
|
+
files.push(*dirs)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns the currently configured directories.
|
160
|
+
#
|
161
|
+
# @return [Array<String>] the configured directories
|
162
|
+
def self.directories
|
163
|
+
@directories ||= \
|
164
|
+
Countless.configuration.annotations_directories.deep_dup
|
165
|
+
end
|
166
|
+
|
167
|
+
# Registers additional directories to be included.
|
168
|
+
#
|
169
|
+
# @param dirs [Array<String>] the additional directories to include
|
170
|
+
def self.register_directories(*dirs)
|
171
|
+
directories.push(*dirs)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns the currently configured tags.
|
175
|
+
#
|
176
|
+
# @return [Array<String>] the configured tags
|
177
|
+
def self.tags
|
178
|
+
@tags ||= Countless.configuration.annotation_tags.deep_dup.map do |tag|
|
179
|
+
"@?#{tag}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Registers additional tags.
|
184
|
+
#
|
185
|
+
# @param additional_tags [Array<String>] the additional tags to include
|
186
|
+
def self.register_tags(*additional_tags)
|
187
|
+
tags.push(*additional_tags)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns the currently configured file extension handlers.
|
191
|
+
#
|
192
|
+
# @return [Hash<RegExp => Proc>] the configured file extension handlers
|
193
|
+
def self.extensions
|
194
|
+
@extensions ||= begin
|
195
|
+
patterns = Countless.configuration.annotation_patterns.values
|
196
|
+
patterns.map do |conf|
|
197
|
+
[
|
198
|
+
extensions_regexp(conf[:extensions], conf[:files] || []),
|
199
|
+
conf[:regex]
|
200
|
+
]
|
201
|
+
end.to_h
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Registers new annotations file extension handlers.
|
206
|
+
#
|
207
|
+
# @param exts [Array<String>] the file extensions to match
|
208
|
+
# @param block [Proc] the line/comment/annotation matching block
|
209
|
+
def self.register_extensions(*exts, &block)
|
210
|
+
extensions[extensions_regexp(exts)] = block
|
211
|
+
end
|
212
|
+
|
213
|
+
# Build a new extension regexp of the given extensions.
|
214
|
+
#
|
215
|
+
# @param exts [Array<String>] the file extensions to join
|
216
|
+
# @param files [Array<String>] a list of dedicated files
|
217
|
+
# @return [RegExp] the extensions matching regexp
|
218
|
+
def self.extensions_regexp(exts, files = [])
|
219
|
+
exts = /\.(#{exts.join('|')})$/
|
220
|
+
return exts if files.empty?
|
221
|
+
|
222
|
+
Regexp.union(/^#{files.join('|')}$/, exts)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns a representation of the annotation that looks like this:
|
226
|
+
#
|
227
|
+
# [126] [TODO] This algorithm is nice and simple, make it faster.
|
228
|
+
#
|
229
|
+
# If +options+ has a flag +:tag+ the tag is shown as in the example
|
230
|
+
# above. Otherwise the string contains just line and text. When
|
231
|
+
# +options+ has a value for +:indent+ the line number block will be
|
232
|
+
# right-justified.
|
233
|
+
#
|
234
|
+
# @param options [Hash{Symbol => Mixed}] the additional options
|
235
|
+
def to_s(options = {})
|
236
|
+
s = +"[#{line.to_s.rjust(options[:indent])}] "
|
237
|
+
s << "[#{tag}] " if options[:tag]
|
238
|
+
s << text
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
# rubocop:enable Metrics/ClassLength
|
243
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Countless
|
4
|
+
# A simple wrapper around the CLOC utility.
|
5
|
+
class Cloc
|
6
|
+
class << self
|
7
|
+
# Extract code statistics from the given files with CLOC. Each key of the
|
8
|
+
# resulting hash is the file path which was inspected. Each value of the
|
9
|
+
# resulting hash contains the raw statistic numbers (blank, comment,
|
10
|
+
# code, total).
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# {
|
15
|
+
# "/app/lib/countless/configuration.rb" => {
|
16
|
+
# :blank=>24, :comment=>43, :code=>141, :total=>208
|
17
|
+
# }
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# @return [Hash{String => Hash{Symbol => Integer}}] the
|
21
|
+
# re-structured CLOC statistics
|
22
|
+
def stats(*paths)
|
23
|
+
raw_stats(*paths).except('SUM', 'header').transform_values do |obj|
|
24
|
+
obj.symbolize_keys.except(:language).tap do |stats|
|
25
|
+
stats[:total] = stats.values.sum
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Fetch the raw statistics via CLOC for the given paths.
|
31
|
+
#
|
32
|
+
# @param paths [Array<String, Pathname>] the paths (files or
|
33
|
+
# directories) to fetch the statistics for
|
34
|
+
# @return [Hash{String => Hash{String => Mixed}] the raw CLOC
|
35
|
+
# YAML output
|
36
|
+
#
|
37
|
+
# rubocop:disable Metrics/MethodLength because of the system
|
38
|
+
# command preparation
|
39
|
+
def raw_stats(*paths)
|
40
|
+
cmd = [
|
41
|
+
Countless.cloc_path,
|
42
|
+
'--quiet',
|
43
|
+
'--by-file',
|
44
|
+
'--yaml',
|
45
|
+
'--list-file -',
|
46
|
+
'2>/dev/null'
|
47
|
+
].join(' ')
|
48
|
+
|
49
|
+
# We pipe in the file list via stdin to cloc, this allows us to
|
50
|
+
# pass large file lists down (ARGV is size limited)
|
51
|
+
stdout = IO.popen(cmd, File::RDWR) do |io|
|
52
|
+
paths.each { |path| io.puts(path) }
|
53
|
+
io.close_write
|
54
|
+
io.read
|
55
|
+
end
|
56
|
+
|
57
|
+
# When the system command was not successful,
|
58
|
+
# we return an fallback result
|
59
|
+
return {} unless $CHILD_STATUS.success?
|
60
|
+
|
61
|
+
# Otherwise we use the CLOC produced YAML and parse it
|
62
|
+
YAML.safe_load(stdout) || {}
|
63
|
+
end
|
64
|
+
# rubocop:enable Metrics/MethodLength
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|