defmastership 1.3.0 → 1.3.2

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +7 -44
  3. data/Gemfile +29 -57
  4. data/bin/defmastership +1 -109
  5. data/config/mutant.yml +27 -22
  6. data/config/rubocop.yml +5 -2
  7. data/defmastership.gemspec +9 -8
  8. data/features/changeref.feature +112 -1
  9. data/features/definition_version.feature +41 -1
  10. data/features/defmastership_version.feature +9 -0
  11. data/features/step_definitions/git_steps.rb +1 -1
  12. data/lib/defmastership/app.rb +116 -0
  13. data/lib/defmastership/batch_modifier.rb +2 -0
  14. data/lib/defmastership/config_preserver.rb +45 -0
  15. data/lib/defmastership/definition.rb +4 -2
  16. data/lib/defmastership/document.rb +6 -1
  17. data/lib/defmastership/export/body_formatter.rb +0 -2
  18. data/lib/defmastership/export/header_formatter.rb +0 -2
  19. data/lib/defmastership/hash_merge_no_new.rb +24 -0
  20. data/lib/defmastership/modifier/change_ref.rb +72 -7
  21. data/lib/defmastership/modifier/factory.rb +5 -0
  22. data/lib/defmastership/modifier/rename_included_files.rb +1 -3
  23. data/lib/defmastership/modifier/update_def.rb +1 -3
  24. data/lib/defmastership/modifier/update_def_checksum.rb +1 -1
  25. data/lib/defmastership/modifier/update_def_version.rb +4 -5
  26. data/lib/defmastership/version.rb +1 -1
  27. data/spec/spec_helper.rb +1 -10
  28. data/spec/unit/defmastership/app_spec.rb +235 -0
  29. data/spec/unit/defmastership/config_preserver_spec.rb +127 -0
  30. data/spec/unit/defmastership/export/csv/formatter_spec.rb +0 -2
  31. data/spec/unit/defmastership/hash_spec.rb +27 -0
  32. data/spec/unit/defmastership/modifier/change_ref_spec.rb +116 -1
  33. data/spec/unit/defmastership/modifier/modifier_common_spec.rb +0 -2
  34. data/spec/unit/defmastership/modifier/update_def_spec.rb +0 -2
  35. data/spec/unit/defmastership/modifier/update_def_version_spec.rb +4 -4
  36. metadata +39 -24
  37. data/lib/defmastership/set_join_hack.rb +0 -13
  38. data/lib/defmastership.rb +0 -19
@@ -0,0 +1,116 @@
1
+ # Copyright (c) 2020 Jerome Arbez-Gindre
2
+ # frozen_string_literal: true
3
+
4
+ require('csv')
5
+ require('defmastership/batch_modifier')
6
+ require('defmastership/config_preserver')
7
+ require('defmastership/document')
8
+ require('defmastership/export/csv/formatter')
9
+ require('defmastership/version')
10
+ require('gli')
11
+
12
+ module Defmastership
13
+ # The start of everything !
14
+ class App
15
+ extend GLI::App
16
+
17
+ program_desc 'Tool to handle Asciidoctor definition extension'
18
+ subcommand_option_handling :normal
19
+ arguments :strict
20
+
21
+ version Defmastership::VERSION
22
+
23
+ # Helper method
24
+ def self.configure_export_flags(command)
25
+ command.flag(%i[separator sep s], default_value: ',', desc: 'CSV separator')
26
+ command.switch(%i[no-fail], desc: 'Exit success even in case of wrong explicit checksum')
27
+ end
28
+
29
+ desc 'Export the definition database in CSV'
30
+ arg 'asciidoctor_file'
31
+ command :export do |command|
32
+ configure_export_flags(command)
33
+ command.action do |_global_options, options, args|
34
+ @results = { number_of_args_give_to_action: args.size }
35
+
36
+ my_doc = Defmastership::Document.new
37
+ my_doc.parse_file_with_preprocessor(args.first)
38
+
39
+ output_file = args.first.sub(/\.adoc$/, '.csv')
40
+
41
+ Defmastership::Export::CSV::Formatter.new(my_doc, options['separator']).export_to(output_file)
42
+
43
+ if my_doc.wrong_explicit_checksum?
44
+ my_doc.definitions.each do |definition|
45
+ next if definition.wrong_explicit_checksum.nil?
46
+
47
+ warn(
48
+ "warning: #{definition.reference} has a wrong explicit " \
49
+ "checksum (should be #{definition.sha256_short})"
50
+ )
51
+ end
52
+ exit 1 unless options[:'no-fail']
53
+ end
54
+ end
55
+ end
56
+
57
+ # Helper method
58
+ def self.configure_modify_flags(command)
59
+ configure_modify_modifications_flag(command)
60
+ configure_modify_modifications_file_flag(command)
61
+ configure_modify_changes_summary_flag(command)
62
+ end
63
+
64
+ # Helper method
65
+ def self.configure_modify_modifications_flag(command)
66
+ command.flag(
67
+ %i[modifications mod m],
68
+ must_match: /(?:[\w-]+)(?:,[\w-]+)*/,
69
+ default_value: 'all',
70
+ desc: 'comma separated list of modifications to apply'
71
+ )
72
+ end
73
+
74
+ # Helper method
75
+ def self.configure_modify_modifications_file_flag(command)
76
+ command.flag(
77
+ %i[modifications-file mf],
78
+ default_value: 'modifications.yml',
79
+ desc: 'modifications description file'
80
+ )
81
+ end
82
+
83
+ # Helper method
84
+ def self.configure_modify_changes_summary_flag(command)
85
+ command.flag(
86
+ %i[changes-summary s],
87
+ desc: 'generates a change summary in a CSV file'
88
+ )
89
+ end
90
+
91
+ desc 'Apply one or more modifications'
92
+ arg 'asciidoctor_file'
93
+ arg 'asciidoctor_file', %i[optional multiple]
94
+ command :modify do |command|
95
+ configure_modify_flags(command)
96
+
97
+ command.action do |_global_options, options, args|
98
+ @results = { number_of_args_give_to_action: args.size }
99
+ config_preserver = Defmastership::ConfigPreserver.new(options[:'modifications-file'])
100
+ changer = BatchModifier.new(config_preserver.config, args.to_h { |afile| [afile, File.read(afile)] })
101
+ changer.apply(options[:modifications].split(/\s*,\s*/).map(&:to_sym))
102
+ changer.adoc_sources.each do |adoc_filename, adoc_text|
103
+ File.write(adoc_filename, adoc_text)
104
+ end
105
+ config_preserver.save(changer.config)
106
+
107
+ unless options['changes-summary'].nil?
108
+ CSV.open(options['changes-summary'], 'wb') do |csv|
109
+ csv << %w[Modifier Was Becomes]
110
+ changer.changes.each { |row| csv << row }
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) 2020 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
+ require('defmastership/modifier/factory')
5
+
4
6
  module Defmastership
5
7
  # Apply modications on a list of related asciidoc sources
6
8
  class BatchModifier
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2025 Jerome Arbez-Gindre
2
+ # frozen_string_literal: true
3
+
4
+ require('defmastership/hash_merge_no_new')
5
+ require('yaml')
6
+
7
+ module Defmastership
8
+ # The configuration can be modified by Mofifiers (e.g. ChangeRef).
9
+ # The configuration file need to include these modifications without:
10
+ # - saving default values
11
+ # - removing empty line
12
+ # - removing comments
13
+ class ConfigPreserver
14
+ def initialize(filename)
15
+ @filename = filename
16
+ end
17
+
18
+ # Get the yaml config from configuration file
19
+ #
20
+ # @return [Hash] the configuration
21
+ def config
22
+ # idiomatic way to perform a deep clone of a hash
23
+ Marshal.load(Marshal.dump(YAML.load_file(@filename)))
24
+ end
25
+
26
+ # Save the updated configuration taking into account blank lines and comments
27
+ #
28
+ # @return [Hash] the configuration
29
+ def save(new_config)
30
+ # we don't wan't to add new configuration items from default values
31
+ new_config_content = config.merge_no_new(new_config).to_yaml.split("\n")
32
+
33
+ # Put in there place the emty lines and comments
34
+ File.open(@filename, 'r').each_line(chomp: true).with_index do |line, line_num|
35
+ if /\A\s*(\#.*)?\z/.match?(line)
36
+ new_config_content.insert(line_num, line)
37
+ elsif line =~ /\A\s*[^\s].+?(\s*\#.*)\z/
38
+ new_config_content[line_num] += Regexp.last_match(1)
39
+ end
40
+ end
41
+
42
+ File.write(@filename, "#{new_config_content.join("\n")}\n")
43
+ end
44
+ end
45
+ end
@@ -1,7 +1,8 @@
1
1
  # Copyright (c) 2020 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'digest'
4
+ require('digest')
5
+ require('forwardable')
5
6
 
6
7
  # Contains the content of a Defmastership definition
7
8
  module Defmastership
@@ -15,7 +16,7 @@ module Defmastership
15
16
  labels.merge(match[:labels].split(/\s*,\s*/).to_set) if match[:labels]
16
17
  labels
17
18
  end,
18
- eref: ->(_) { Hash.new([]) },
19
+ eref: ->(_) { Hash.new { |hash, key| hash[key] = [] } },
19
20
  iref: ->(_) { [] },
20
21
  attributes: ->(_) { {} },
21
22
  explicit_checksum: ->(match) { match[:explicit_checksum] },
@@ -43,6 +44,7 @@ module Defmastership
43
44
  # Defmastership definition: contains all data of a definition
44
45
  class Definition
45
46
  extend Forwardable
47
+
46
48
  def_delegators :@data,
47
49
  :type,
48
50
  :reference,
@@ -4,7 +4,11 @@
4
4
  require('asciidoctor')
5
5
  require('defmastership/core/constants')
6
6
  require('defmastership/core/parsing_state')
7
+ require('defmastership/definition')
8
+ require('defmastership/definition_parser')
9
+ require('defmastership/filters')
7
10
  require('defmastership/matching_line')
11
+ require('forwardable')
8
12
 
9
13
  # Contains the content of a Defmastership document: mainly definitions
10
14
  module Defmastership
@@ -75,6 +79,7 @@ module Defmastership
75
79
  # Reflects document structure from a definition point of view
76
80
  class Document
77
81
  extend Forwardable
82
+
78
83
  def_delegators :@data, :definitions, :labels, :eref, :iref, :attributes, :variables
79
84
 
80
85
  def initialize
@@ -134,7 +139,7 @@ module Defmastership
134
139
  # @param reference [String] the defintion from the reference
135
140
  # @return [Definition] the defintion from the reference
136
141
  def ref_to_def(reference)
137
- definitions.find { |definition| definition.reference == reference }
142
+ definitions.find { |definition| definition.reference.eql?(reference) }
138
143
  end
139
144
 
140
145
  private
@@ -1,8 +1,6 @@
1
1
  # Copyright (c) 2020 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
- require('defmastership/set_join_hack')
5
-
6
4
  module Defmastership
7
5
  module Export
8
6
  # format CSV lines per definition
@@ -1,8 +1,6 @@
1
1
  # Copyright (c) 2020 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
- require('csv')
5
-
6
4
  module Defmastership
7
5
  module Export
8
6
  # format CSV header (first line) for one document
@@ -0,0 +1,24 @@
1
+ # Copyright (c) 2025 Jerome Arbez-Gindre
2
+ # frozen_string_literal: true
3
+
4
+ # adding a method to hash to allow to merge two hases only by modifying aready existing entries
5
+ class Hash
6
+ # Merges another hash by updating only the values for keys that already exist.
7
+ # It does not add new keys from the other hash. The merge is recursive for nested hashes.
8
+ # @param other_hash [Hash] The hash to merge from.
9
+ # @return [Hash] The new, merged hash.
10
+ def merge_no_new(other_hash)
11
+ # We use #merge with a block to handle key "collisions".
12
+ # We only merge the keys from `other_hash` that already exist in `self`
13
+ # by using `other_hash.slice(*keys)`.
14
+ merge(other_hash.slice(*keys)) do |_key, old_val, new_val|
15
+ if old_val.is_a?(Hash) && new_val.is_a?(Hash)
16
+ # Recursive call if both values are hashes
17
+ old_val.merge_no_new(new_val)
18
+ else
19
+ # else, we simply take the new value
20
+ new_val
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,9 +1,9 @@
1
1
  # Copyright (c) 2020 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'defmastership/core/constants'
5
- require('defmastership/core/parsing_state')
6
- require 'defmastership/modifier/modifier_common'
4
+ require('defmastership/core/constants')
5
+ require('defmastership/modifier/modifier_common')
6
+ require('facets/file/writelines')
7
7
 
8
8
  module Defmastership
9
9
  module Modifier
@@ -12,7 +12,7 @@ module Defmastership
12
12
  include ModifierCommon
13
13
 
14
14
  # [Regexp] match all text before the definition's reference
15
- DEF_BEFORE_REF = <<~"BEF"
15
+ DEF_BEFORE_REF = <<~"BEF".freeze
16
16
  ^
17
17
  \\s*
18
18
  \\[
@@ -24,7 +24,7 @@ module Defmastership
24
24
  private_constant :DEF_BEFORE_REF
25
25
 
26
26
  # [Regexp] match all text after the definition's reference
27
- DEF_AFTER_REF = <<~"AFT"
27
+ DEF_AFTER_REF = <<~"AFT".freeze
28
28
  \\s*
29
29
  #{Core::DMRegexp::DEF_SUMMARY}
30
30
  #{Core::DMRegexp::DEF_LABELS}
@@ -57,7 +57,7 @@ module Defmastership
57
57
  #
58
58
  # @return [Array<Symbol>] the two symbols of replacement methods
59
59
  def self.replacement_methods
60
- %i[replace_refdef replace_irefs]
60
+ %i[replace_refdef replace_irefs replace_include_tags replace_tags_in_included_files]
61
61
  end
62
62
 
63
63
  # @return [Hash{Symbol => Object}] the default configuration
@@ -100,6 +100,29 @@ module Defmastership
100
100
  end
101
101
  end
102
102
 
103
+ # Replace the definition's refs in tags of include statements
104
+ #
105
+ # @param line [String] the current line
106
+ # @return [String] the modified line
107
+ def replace_include_tags(line)
108
+ return line unless line.match?(Core::DMRegexp::INCLUDE)
109
+
110
+ changes.reduce(line) do |res_line, (from, to)|
111
+ res_line.sub(/(?<before>tags?=!?([^;]*;)?)#{from}\b/) { "#{$LAST_MATCH_INFO[:before]}#{to}" }
112
+ end
113
+ end
114
+
115
+ # Replace the definition's refs in included files
116
+ #
117
+ # @param line [String] the current line
118
+ # @return [String] the (unmodified) line
119
+ def replace_tags_in_included_files(line)
120
+ if line.match(Core::DMRegexp::INCLUDE)
121
+ Helper.replace_tags_in_included_files(Helper::ParsedFilename.full_filename(Regexp.last_match), changes)
122
+ end
123
+ line
124
+ end
125
+
103
126
  private
104
127
 
105
128
  def do_replace_refdef(line)
@@ -134,12 +157,54 @@ module Defmastership
134
157
  Regexp.new(regexp_str, Regexp::EXTENDED)
135
158
  end
136
159
 
137
- # @param match [MatchData] The match form Regepxp match
160
+ # @param match [MatchData] The match from Regexp match
138
161
  # @param replacement [String] the reference replacement text
139
162
  # @return [String] the overall replaced text
140
163
  def self.text_with(match, replacement)
141
164
  match[:before] + replacement + (match[:version_and_checksum] || '') + match[:after]
142
165
  end
166
+
167
+ # Replace the definition's refs in included files
168
+ #
169
+ # @param filename [String] the included file name
170
+ # @param changes [Array<Array<String>>] the changes to apply
171
+ # @return [void]
172
+ def self.replace_tags_in_included_files(filename, changes)
173
+ lines = File.readlines(filename)
174
+
175
+ new_lines =
176
+ lines.map do |included_line|
177
+ apply_changes_in_included_file_line(included_line, changes)
178
+ end
179
+
180
+ File.writelines(filename, new_lines)
181
+ end
182
+
183
+ # Do the concrete replacements in included files
184
+ #
185
+ # @param line [String] The line from the included file
186
+ # @param changes [Array<Array<String>>] the changes to apply
187
+ # @return [String] the modified line with all changes
188
+ def self.apply_changes_in_included_file_line(line, changes)
189
+ changes.reduce(line) do |res_line, (from, to)|
190
+ if res_line.match?(/(tag|end)::#{from}\[\]/)
191
+ res_line.sub(from, to)
192
+ else
193
+ res_line
194
+ end
195
+ end
196
+ end
197
+
198
+ # Build filename from match data
199
+ module ParsedFilename
200
+ # build the full filename from the match of an include statement
201
+ #
202
+ # @param match [MatchData] The match from Regexp match
203
+ # @return [String] the overall filename
204
+ def self.full_filename(match)
205
+ (match[:path] || '') + match[:filename]
206
+ end
207
+ end
143
208
  end
144
209
  private_constant :Helper
145
210
  end
@@ -1,6 +1,11 @@
1
1
  # Copyright (c) 2023 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
+ require('defmastership/modifier/change_ref')
5
+ require('defmastership/modifier/rename_included_files')
6
+ require('defmastership/modifier/update_def_checksum')
7
+ require('defmastership/modifier/update_def_version')
8
+
4
9
  module Defmastership
5
10
  module Modifier
6
11
  # build modifiers from a piece of configuration
@@ -1,9 +1,7 @@
1
1
  # Copyright (c) 2020 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
- require('defmastership/core/constants')
5
- require('defmastership/matching_line')
6
- require('defmastership/modifier/modifier_common')
4
+ require('defmastership/filters')
7
5
 
8
6
  module Defmastership
9
7
  # defintion of the Rename Included Files Modifier
@@ -1,8 +1,6 @@
1
1
  # Copyright (c) 2024 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
- require('defmastership/core/constants')
5
-
6
4
  module Defmastership
7
5
  module Modifier
8
6
  # @abstract Subclass and define +reference_replacement+ to implement a
@@ -62,7 +60,7 @@ module Defmastership
62
60
  match = line.match(Core::DMRegexp::DEFINITION)
63
61
 
64
62
  return line unless match
65
- return line unless match[:type] == def_type
63
+ return line unless match[:type].eql?(def_type)
66
64
 
67
65
  reference = match[:reference]
68
66
  line.sub(self.class.reference_regexp(reference), reference_replacement(reference, match))
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2020 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'defmastership/modifier/update_def'
4
+ require('defmastership/modifier/update_def')
5
5
 
6
6
  module Defmastership
7
7
  module Modifier
@@ -1,9 +1,8 @@
1
1
  # Copyright (c) 2020 Jerome Arbez-Gindre
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'defmastership/modifier/update_def'
5
- require 'git'
6
- require 'tmpdir'
4
+ require('defmastership/modifier/update_def')
5
+ require('git')
7
6
 
8
7
  module Defmastership
9
8
  module Modifier
@@ -36,7 +35,7 @@ module Defmastership
36
35
  # * :key filename
37
36
  # * :value file content
38
37
  def do_modifications(adoc_sources)
39
- if ref_tag == ''
38
+ if ref_tag.eql?('')
40
39
  ref_document.each { |ref_doc| @ref_document.parse_file_with_preprocessor(ref_doc) }
41
40
  else
42
41
  Dir.mktmpdir('defmastership') do |tmpdir|
@@ -92,7 +91,7 @@ module Defmastership
92
91
  # @return [String] the current definition version
93
92
  def self.ref_version(ref_definition, definition, first_version)
94
93
  new_ref_version = ref_definition.explicit_version
95
- return new_ref_version if definition.sha256_short == ref_definition.sha256_short
94
+ return new_ref_version if definition.sha256_short.eql?(ref_definition.sha256_short)
96
95
 
97
96
  new_ref_version ? new_ref_version.next : first_version
98
97
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Defmastership
5
5
  # [String] Gem version
6
- VERSION = '1.3.0'
6
+ VERSION = '1.3.2'
7
7
  public_constant :VERSION
8
8
  end
data/spec/spec_helper.rb CHANGED
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require('aasm/rspec')
5
- require('aruba/rspec')
6
5
 
7
6
  # formatter = [SimpleCov::Formatter::HTMLFormatter]
8
7
  # SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(formatter)
@@ -17,15 +16,9 @@ SimpleCov.start do
17
16
 
18
17
  add_filter 'config'
19
18
  add_filter 'vendor'
20
- add_filter 'set_join_hack'
21
-
22
- minimum_coverage 100
23
19
 
24
20
  enable_coverage :branch
25
- end
26
-
27
- RSpec.configure do |config|
28
- config.include(Aruba::Api)
21
+ minimum_coverage line: 100, branch: 100
29
22
  end
30
23
 
31
24
  RSpec::Matchers.define(:matchdata_including) do |h|
@@ -35,5 +28,3 @@ RSpec::Matchers.define(:matchdata_including) do |h|
35
28
  end
36
29
  end
37
30
  end
38
-
39
- require('defmastership')