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 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