feature_map 1.1.0 → 1.1.1
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 +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
|