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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1243d054f38c9a5520a3790190ad36cf49a447c4e931d7e5cc85cd1a958eb20
4
- data.tar.gz: 275ed70329b4df76a18412719ee004c8626cb53f708905f5ff3413846fab34e2
3
+ metadata.gz: 206bf1a9f4dcf4bb82658ce1a6e7f3c532b5929521e53c070b4efa92eb4b61b1
4
+ data.tar.gz: dbde9980709b0d3a28bb2b7319da42a9bbd188aec2c78c9ecbaef6405a95bf96
5
5
  SHA512:
6
- metadata.gz: 62100534249d7ccc2ec67849ca92bcaf38dc55bd6d22a3d1aefb673b6a6222c16cddbb742af38f895832d758de85ffe75c6f93fccf2b4d6c292d344566b04089
7
- data.tar.gz: e055eaa7af6fe4a6e27c9f29a38e1c2d1a687cbf0f5ba500541a0b2b21cbec9a78b29cc23c95128995eb81142b98cbeee3d01e4dc454084e956f8a748f0b8e95
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 (feature_map.gemspec)[feature_map.gemspec] file.
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.
@@ -9,7 +9,9 @@ module FeatureMap
9
9
  class Cli
10
10
  def self.run!(argv)
11
11
  command = argv.shift
12
- if command == 'validate'
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
- validate - run all validations
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
- for_file - find feature assignment for a single file
34
- for_feature - find assignment information for a feature
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