feature_map 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/feature_map/cli.rb +34 -4
- data/lib/feature_map/private/assignment_applicator.rb +112 -0
- data/lib/feature_map/private/docs/index.html +88 -85
- data/lib/feature_map/private.rb +9 -0
- data/lib/feature_map.rb +5 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 206bf1a9f4dcf4bb82658ce1a6e7f3c532b5929521e53c070b4efa92eb4b61b1
|
4
|
+
data.tar.gz: dbde9980709b0d3a28bb2b7319da42a9bbd188aec2c78c9ecbaef6405a95bf96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03b1baec50bb36335af87eabf62885290cfe53516686bded3ac61d6ae660dd9d181d6df817e1b2089f0e3c605dfdf119bedd334d61ae0c9ee1049fe29ee7cd75
|
7
|
+
data.tar.gz: c2c72319780a7043aa1654cf691d53a576275af18ef968fde2777a13b89bba4208c77daed041ff0def4ec15f0851ce1cc9bdf3c4d724e89af28a7c26cd41a8be
|
data/README.md
CHANGED
@@ -259,7 +259,7 @@ That's it! Assuming you can complete all of these steps without any error or iss
|
|
259
259
|
|
260
260
|
When a new version of the gem is ready to be published, please follow these steps:
|
261
261
|
|
262
|
-
* Update `spec.version` value in the
|
262
|
+
* Update `spec.version` value in the [feature_map.gemspec](feature_map.gemspec) file.
|
263
263
|
* Assign a version to this release in accordance with [Semantic Versioning](https://semver.org/) based on the changes contained in this release.
|
264
264
|
* Create a new release tag in Github ([link](https://github.com/Beyond-Finance/feature_map/releases)) with a value that matches the new Gemspec version.
|
265
265
|
* Checkout the release tag in your local environment.
|
data/lib/feature_map/cli.rb
CHANGED
@@ -9,7 +9,9 @@ module FeatureMap
|
|
9
9
|
class Cli
|
10
10
|
def self.run!(argv)
|
11
11
|
command = argv.shift
|
12
|
-
if command == '
|
12
|
+
if command == 'apply_assignments'
|
13
|
+
apply_assignments!(argv)
|
14
|
+
elsif command == 'validate'
|
13
15
|
validate!(argv)
|
14
16
|
elsif command == 'docs'
|
15
17
|
docs!(argv)
|
@@ -26,19 +28,47 @@ module FeatureMap
|
|
26
28
|
Usage: bin/featuremap <subcommand>
|
27
29
|
|
28
30
|
Subcommands:
|
29
|
-
|
31
|
+
apply_assignments - applies specified feature assignments to source files
|
30
32
|
docs - generates feature documentation
|
33
|
+
for_feature - find assignment information for a feature
|
34
|
+
for_file - find feature assignment for a single file
|
31
35
|
test_coverage - generates per-feature test coverage statistics
|
32
36
|
test_pyramid - generates per-feature test pyramid (unit, integration, regression) statistics
|
33
|
-
|
34
|
-
|
37
|
+
validate - run all validations
|
38
|
+
|
39
|
+
##################################################
|
35
40
|
help - display help information about feature_map
|
41
|
+
##################################################
|
36
42
|
USAGE
|
37
43
|
else
|
38
44
|
puts "'#{command}' is not a feature_map command. See `bin/featuremap help`."
|
39
45
|
end
|
40
46
|
end
|
41
47
|
|
48
|
+
def self.apply_assignments!(argv)
|
49
|
+
parser = OptionParser.new do |opts|
|
50
|
+
opts.banner = <<~MSG
|
51
|
+
Usage: bin/featuremap apply_assignments [assignments.csv].
|
52
|
+
Note: Expects two fields with no header: dir/filepath,feature
|
53
|
+
Supports assignments in the following filetypes:
|
54
|
+
cls,html,js,jsx,rb,ts,tsx,xml
|
55
|
+
MSG
|
56
|
+
|
57
|
+
opts.on('--help', 'Shows this prompt') do
|
58
|
+
puts opts
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
end
|
62
|
+
args = parser.order!(argv)
|
63
|
+
parser.parse!(args)
|
64
|
+
non_flag_args = argv.reject { |arg| arg.start_with?('--') }
|
65
|
+
assignments_file_path = non_flag_args[0]
|
66
|
+
|
67
|
+
raise 'Please specify assignments.csv file' if assignments_file_path.nil?
|
68
|
+
|
69
|
+
FeatureMap.apply_assignments!(assignments_file_path)
|
70
|
+
end
|
71
|
+
|
42
72
|
def self.validate!(argv)
|
43
73
|
options = {}
|
44
74
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module FeatureMap
|
5
|
+
module Private
|
6
|
+
class AssignmentApplicator
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(assignments: T::Array[T::Array[T.nilable(String)]]).void }
|
10
|
+
def self.apply_assignments!(assignments)
|
11
|
+
file_to_feature_map = map_files_to_feature
|
12
|
+
assignments.each do |(filepath, feature)|
|
13
|
+
next puts("Missing data: #{filepath}, #{feature}") unless filepath && feature
|
14
|
+
next puts("Already assigned: #{filepath}, #{feature}") if file_to_feature_map[filepath]
|
15
|
+
|
16
|
+
apply_assignment(filepath, feature)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { params(filepath: String, feature: String).void }
|
21
|
+
def self.apply_assignment(filepath, feature)
|
22
|
+
return apply_to_directory(filepath, feature) if File.directory?(filepath)
|
23
|
+
|
24
|
+
# NOTE: For simplicity we're reading the entire file into system memory
|
25
|
+
# and then writing it back out. This breaks in theory for exceptionally
|
26
|
+
# large source files on very resource-constrained machines. In practice it's
|
27
|
+
# probably fine.
|
28
|
+
file = File.readlines(filepath)
|
29
|
+
case File.extname(filepath)
|
30
|
+
when '.cls'
|
31
|
+
apply_to_apex(file, filepath, feature)
|
32
|
+
when '.html'
|
33
|
+
apply_to_html(file, filepath, feature)
|
34
|
+
when '.js', '.jsx', '.ts', '.tsx'
|
35
|
+
apply_to_javascript(file, filepath, feature)
|
36
|
+
when '.rb'
|
37
|
+
apply_to_ruby(file, filepath, feature)
|
38
|
+
when '.xml'
|
39
|
+
apply_to_xml(file, filepath, feature)
|
40
|
+
else
|
41
|
+
puts "Cannot auto assign #{filepath} to #{feature}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { params(file: T::Array[String], filepath: String, feature: String).void }
|
46
|
+
def self.apply_to_apex(file, filepath, feature)
|
47
|
+
File.open(filepath, 'w') do |f|
|
48
|
+
f.write("// @feature #{feature}\n\n")
|
49
|
+
file.each { |line| f.write(line) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { params(filepath: String, feature: String).void }
|
54
|
+
def self.apply_to_directory(filepath, feature)
|
55
|
+
feature_path = File.join(filepath, '.feature')
|
56
|
+
|
57
|
+
File.write(feature_path, "#{feature}\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
sig { params(file: T::Array[String], filepath: String, feature: String).void }
|
61
|
+
def self.apply_to_html(file, filepath, feature)
|
62
|
+
File.open(filepath, 'w') do |f|
|
63
|
+
f.write("<!-- @feature #{feature} -->\n\n")
|
64
|
+
file.each { |line| f.write(line) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(file: T::Array[String], filepath: String, feature: String).void }
|
69
|
+
def self.apply_to_javascript(file, filepath, feature)
|
70
|
+
File.open(filepath, 'w') do |f|
|
71
|
+
f.write("// @feature #{feature}\n\n")
|
72
|
+
file.each { |line| f.write(line) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { params(file: T::Array[String], filepath: String, feature: String).void }
|
77
|
+
def self.apply_to_ruby(file, filepath, feature)
|
78
|
+
File.open(filepath, 'w') do |f|
|
79
|
+
# NOTE: No spacing newline; doing so would separate
|
80
|
+
# the feature declaration into the only "first" comment
|
81
|
+
# section, which breaks existing magic comments.
|
82
|
+
# https://docs.ruby-lang.org/en/3.1/syntax/comments_rdoc.html#label-Magic+Comments
|
83
|
+
|
84
|
+
f.write("# @feature #{feature}\n")
|
85
|
+
file.each { |line| f.write(line) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { params(file: T::Array[String], filepath: String, feature: String).void }
|
90
|
+
def self.apply_to_xml(file, filepath, feature)
|
91
|
+
# NOTE: Installation of top-level comments in some XML files (notably, in Salesforce)
|
92
|
+
# breaks parsing. Instead, we'll insert them right after the opening XML declaration.
|
93
|
+
xml_declaration = file.index { |line| line =~ /<\?xml/i }
|
94
|
+
insert_index = xml_declaration.nil? ? 0 : xml_declaration + 1
|
95
|
+
file.insert(insert_index, "<!-- @feature #{feature} -->\n\n")
|
96
|
+
|
97
|
+
File.open(filepath, 'w') do |f|
|
98
|
+
file.each { |line| f.write(line) }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { returns(T::Hash[String, String]) }
|
103
|
+
def self.map_files_to_feature
|
104
|
+
Private.feature_file_assignments.reduce({}) do |content, (feature_name, files)|
|
105
|
+
mapped_files = files.to_h { |f| [f, feature_name] }
|
106
|
+
|
107
|
+
content.merge(mapped_files)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|