giblish 2.2.2 → 3.0.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.
@@ -0,0 +1,137 @@
1
+ module Giblish
2
+ # Provides a unified interface for node data by explicitly composing
3
+ # multiple providers into a single object.
4
+ #
5
+ # Supports dynamic composition - providers can be added after initialization.
6
+ # Each method explicitly delegates to the first provider that responds to it.
7
+ class NodeDataProvider
8
+ def initialize(*providers)
9
+ @providers = Array(providers).compact
10
+ end
11
+
12
+ def add_provider(provider)
13
+ @providers << provider
14
+ self
15
+ end
16
+
17
+ # === Source Node Interface (DocAttrBuilder + AdocSrcProvider) ===
18
+
19
+ def document_attributes(src_node, dst_node, dst_top)
20
+ provider = find_provider(:document_attributes)
21
+ provider.document_attributes(src_node, dst_node, dst_top)
22
+ end
23
+
24
+ def adoc_source(src_node, dst_node, dst_top)
25
+ provider = find_provider(:adoc_source)
26
+ provider.adoc_source(src_node, dst_node, dst_top)
27
+ end
28
+
29
+ def api_options(src_node, dst_node, dst_top)
30
+ provider = find_provider(:api_options, raise_on_missing: false)
31
+ return {} unless provider
32
+ provider.api_options(src_node, dst_node, dst_top)
33
+ end
34
+
35
+ # === Conversion Info Interface (SuccessfulConversion / FailedConversion) ===
36
+
37
+ def src_node
38
+ provider = find_provider(:src_node)
39
+ provider.src_node
40
+ end
41
+
42
+ def dst_node
43
+ provider = find_provider(:dst_node)
44
+ provider.dst_node
45
+ end
46
+
47
+ def dst_top
48
+ provider = find_provider(:dst_top)
49
+ provider.dst_top
50
+ end
51
+
52
+ def converted
53
+ provider = find_provider(:converted)
54
+ provider.converted
55
+ end
56
+
57
+ def src_rel_path
58
+ provider = find_provider(:src_rel_path)
59
+ provider.src_rel_path
60
+ end
61
+
62
+ def src_basename
63
+ provider = find_provider(:src_basename)
64
+ provider.src_basename
65
+ end
66
+
67
+ def title
68
+ provider = find_provider(:title)
69
+ provider.title
70
+ end
71
+
72
+ def docid
73
+ provider = find_provider(:docid)
74
+ provider.docid
75
+ end
76
+
77
+ def to_s
78
+ provider = find_provider(:to_s)
79
+ provider.to_s
80
+ end
81
+
82
+ # SuccessfulConversion specific
83
+ def stderr
84
+ provider = find_provider(:stderr)
85
+ provider.stderr
86
+ end
87
+
88
+ def adoc
89
+ provider = find_provider(:adoc)
90
+ provider.adoc
91
+ end
92
+
93
+ def dst_rel_path
94
+ provider = find_provider(:dst_rel_path)
95
+ provider.dst_rel_path
96
+ end
97
+
98
+ def purpose_str
99
+ provider = find_provider(:purpose_str)
100
+ provider.purpose_str
101
+ end
102
+
103
+ # FailedConversion specific
104
+ def error_msg
105
+ provider = find_provider(:error_msg)
106
+ provider.error_msg
107
+ end
108
+
109
+ # === History Interface (FileHistory) ===
110
+
111
+ def history
112
+ provider = find_provider(:history, raise_on_missing: false)
113
+ return nil unless provider
114
+ provider.history
115
+ end
116
+
117
+ def branch
118
+ provider = find_provider(:branch, raise_on_missing: false)
119
+ return nil unless provider
120
+ provider.branch
121
+ end
122
+
123
+ def respond_to?(method_name, include_private = false)
124
+ find_provider(method_name, raise_on_missing: false) ? true : super
125
+ end
126
+
127
+ private
128
+
129
+ def find_provider(method_name, raise_on_missing: true)
130
+ provider = @providers.find { |p| p.respond_to?(method_name) }
131
+ if provider.nil? && raise_on_missing
132
+ raise NoMethodError, "None of the providers respond to '#{method_name}'"
133
+ end
134
+ provider
135
+ end
136
+ end
137
+ end
@@ -1,4 +1,5 @@
1
1
  require "pathname"
2
+ require "gran"
2
3
 
3
4
  module Giblish
4
5
  # Provides relevant paths for layout resources based on the given options
@@ -54,7 +55,7 @@ module Giblish
54
55
  return if @src_resource_dir_abs.nil?
55
56
 
56
57
  # Tweak paths based on the content of a given resource dir
57
- file_tree = PathTree.build_from_fs(@src_resource_dir_abs)
58
+ file_tree = Gran::PathTree.build_from_fs(@src_resource_dir_abs)
58
59
 
59
60
  @src_style_path_rel = find_style_file(file_tree, cmd_opts.format, cmd_opts.style_name)
60
61
  @src_style_path_abs = @src_resource_dir_abs / @src_style_path_rel if @src_style_path_rel
@@ -186,7 +187,7 @@ module Giblish
186
187
  return if @asset_regex.nil?
187
188
 
188
189
  # build a tree with all dirs matching the given regexp
189
- st = PathTree.build_from_fs(@srcdir, prune: true) do |p|
190
+ st = Gran::PathTree.build_from_fs(@srcdir, prune: true) do |p|
190
191
  p.directory? && @asset_regex =~ p.to_s
191
192
  end
192
193
 
@@ -17,7 +17,7 @@ module Giblish
17
17
  end
18
18
 
19
19
  def css_path
20
- @parameters.key?("css-path") && !@parameters["css-path"].empty? ? Pathname.new(@parameters["css-path"]) : nil
20
+ (@parameters.key?("css-path") && !@parameters["css-path"].empty?) ? Pathname.new(@parameters["css-path"]) : nil
21
21
  end
22
22
 
23
23
  def search_assets_top_rel
@@ -33,9 +33,9 @@ module Giblish
33
33
  @parameters.key?("as-regexp")
34
34
  end
35
35
 
36
- def method_missing(meth, *args, &block)
36
+ def method_missing(meth, ...)
37
37
  unless respond_to_missing?(meth)
38
- super(meth, *args, &block)
38
+ super
39
39
  return
40
40
  end
41
41
 
@@ -1,7 +1,7 @@
1
1
  require "pathname"
2
2
  require "json"
3
+ require "gran"
3
4
  require_relative "searchquery"
4
- require_relative "../pathtree"
5
5
 
6
6
  module Giblish
7
7
  # reads all lines in the given file at instantiation and
@@ -21,7 +21,7 @@ module Giblish
21
21
  def wash_line(line)
22
22
  # remove some asciidoctor format sequences
23
23
  # '::', '^|===', '^==', '^--, ':myvar: ...'
24
- r = Regexp.new(/(::+|^[=|]+|^--+|^:\w+:.*$)/)
24
+ r = /(::+|^[=|]+|^--+|^:\w+:.*$)/
25
25
  line.gsub(r, "")
26
26
  end
27
27
  end
@@ -205,7 +205,7 @@ module Giblish
205
205
  def build_src_tree
206
206
  # setup the tree of source files and pro-actively read in all text
207
207
  # into memory
208
- src_tree = PathTree.build_from_fs(@assets_fs_path, prune: false) do |p|
208
+ src_tree = Gran::PathTree.build_from_fs(@assets_fs_path, prune: false) do |p|
209
209
  p.extname.downcase == ".adoc"
210
210
  end
211
211
  src_tree.traverse_preorder do |level, node|
@@ -1,5 +1,5 @@
1
1
  require "pathname"
2
- require_relative "pathtree"
2
+ require "gran"
3
3
 
4
4
  module Giblish
5
5
  class SubtreeSrcItf
@@ -75,7 +75,7 @@ module Giblish
75
75
  # with this object as source for conversion options
76
76
  # and adoc_source
77
77
  v_path = Pathname.new("/virtual") / index_dir / "#{@basename}.adoc"
78
- v_tree = PathTree.new(v_path, self)
78
+ v_tree = Gran::PathTree.new(v_path, self)
79
79
  src_node = v_tree.node(v_path, from_root: true)
80
80
 
81
81
  # add the destination node where the converted file will be stored
@@ -1,7 +1,8 @@
1
1
  require "asciidoctor"
2
2
  require "asciidoctor-pdf"
3
- require_relative "pathtree"
3
+ require "gran"
4
4
  require_relative "conversion_info"
5
+ require_relative "node_data_provider"
5
6
  require_relative "utils"
6
7
 
7
8
  module Giblish
@@ -70,7 +71,7 @@ module Giblish
70
71
 
71
72
  # get the top-most node of the source and destination trees
72
73
  @src_tree = src_top
73
- @dst_tree = PathTree.new(dst_top).node(dst_top, from_root: true)
74
+ @dst_tree = Gran::PathTree.new(dst_top).node(dst_top, from_root: true)
74
75
 
75
76
  # setup build-phase callback objects
76
77
  @pre_builders = Array(opts.fetch(:pre_builders, []))
@@ -127,20 +128,24 @@ module Giblish
127
128
  end
128
129
 
129
130
  # the default callback will tie a 'SuccessfulConversion' instance
130
- # to the destination node as its data
131
+ # to the destination node as its data, wrapped in NodeDataProvider
132
+ # to allow dynamic addition of other providers (e.g., history)
131
133
  def self.on_success(src_node, dst_node, dst_tree, doc, adoc_log_str)
132
- dst_node.data = DataDelegator.new(SuccessfulConversion.new(
134
+ conversion_info = SuccessfulConversion.new(
133
135
  src_node: src_node, dst_node: dst_node, dst_top: dst_tree, adoc: doc, adoc_stderr: adoc_log_str
134
- ))
136
+ )
137
+ dst_node.data = NodeDataProvider.new(conversion_info)
135
138
  end
136
139
 
137
140
  # the default callback will tie a 'FailedConversion' instance
138
- # to the destination node as its data
141
+ # to the destination node as its data, wrapped in NodeDataProvider
142
+ # to allow dynamic addition of other providers (e.g., history)
139
143
  def self.on_failure(src_node, dst_node, dst_tree, ex, adoc_log_str)
140
144
  Giblog.logger.error { ex.message }
141
- dst_node.data = DataDelegator.new(FailedConversion.new(
145
+ conversion_info = FailedConversion.new(
142
146
  src_node: src_node, dst_node: dst_node, dst_top: dst_tree, error_msg: ex.message
143
- ))
147
+ )
148
+ dst_node.data = NodeDataProvider.new(conversion_info)
144
149
  end
145
150
  end
146
151
 
@@ -1,3 +1,3 @@
1
1
  module Giblish
2
- VERSION = "2.2.2".freeze
2
+ VERSION = "3.0.1".freeze
3
3
  end
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "pathname"
5
+ require "optparse"
6
+ require "fileutils"
7
+
8
+ # Release automation script for giblish gem
9
+ class GiblishReleaseManager
10
+ GIBLISH_DIR = Pathname.new(__dir__).parent
11
+
12
+ VALID_COMMANDS = %w[prepare publish all].freeze
13
+ VALID_VERSION_TYPES = %w[major minor patch].freeze
14
+
15
+ attr_reader :command, :version_type, :dry_run, :yes, :specific_version
16
+
17
+ def initialize(args)
18
+ @dry_run = false
19
+ @yes = false
20
+ @specific_version = nil
21
+ parse_options(args)
22
+ validate_args
23
+ end
24
+
25
+ def run
26
+ puts "\n=== Giblish Release Manager ==="
27
+ puts "Command: #{command}"
28
+ puts "Version: #{version_type || specific_version}"
29
+ puts "Dry-run: #{dry_run}"
30
+ puts "================================\n\n"
31
+
32
+ case command
33
+ when "prepare"
34
+ prepare_release
35
+ when "publish"
36
+ publish_release
37
+ when "all"
38
+ prepare_release
39
+ publish_release
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def parse_options(args)
46
+ OptionParser.new do |opts|
47
+ opts.banner = "Usage: release.rb [options] COMMAND [VERSION]"
48
+ opts.separator ""
49
+ opts.separator "COMMAND: #{VALID_COMMANDS.join(", ")}"
50
+ opts.separator "VERSION: #{VALID_VERSION_TYPES.join(", ")} or specific version (e.g., 0.2.0)"
51
+ opts.separator ""
52
+ opts.separator "Options:"
53
+
54
+ opts.on("-n", "--dry-run", "Show what would happen without doing it") do
55
+ @dry_run = true
56
+ end
57
+
58
+ opts.on("-y", "--yes", "Skip confirmation prompts") do
59
+ @yes = true
60
+ end
61
+
62
+ opts.on("-h", "--help", "Show this help message") do
63
+ puts opts
64
+ exit 0
65
+ end
66
+ end.parse!(args)
67
+
68
+ @command = args[0]
69
+
70
+ # Parse version argument
71
+ if args[1]
72
+ if VALID_VERSION_TYPES.include?(args[1])
73
+ @version_type = args[1]
74
+ elsif args[1].match?(/^\d+\.\d+\.\d+$/)
75
+ @specific_version = args[1]
76
+ else
77
+ puts "ERROR: Invalid version '#{args[1]}'. Must be #{VALID_VERSION_TYPES.join(", ")} or a version number (e.g., 0.2.0)"
78
+ exit 1
79
+ end
80
+ end
81
+ end
82
+
83
+ def validate_args
84
+ unless VALID_COMMANDS.include?(command)
85
+ puts "ERROR: Invalid command '#{command}'. Must be one of: #{VALID_COMMANDS.join(", ")}"
86
+ exit 1
87
+ end
88
+
89
+ if command != "publish" && version_type.nil? && specific_version.nil?
90
+ puts "ERROR: VERSION argument required for '#{command}' command"
91
+ exit 1
92
+ end
93
+ end
94
+
95
+ def prepare_release
96
+ puts "\n📦 Preparing giblish for release...\n"
97
+ with_dir(GIBLISH_DIR) do
98
+ run_safety_checks
99
+ check_gran_dependency
100
+ new_version = bump_version(GIBLISH_DIR / "lib/giblish/version.rb")
101
+ update_changelog(new_version)
102
+ build_gem
103
+ create_git_commit_and_tag(new_version)
104
+ success("Giblish prepared for release: v#{new_version}")
105
+ end
106
+ end
107
+
108
+ def publish_release
109
+ puts "\n🚀 Publishing giblish to RubyGems...\n"
110
+ with_dir(GIBLISH_DIR) do
111
+ push_to_github
112
+ publish_gem
113
+ success("Giblish published!")
114
+ end
115
+ end
116
+
117
+ # Safety Checks
118
+ def run_safety_checks
119
+ step("Running safety checks")
120
+ check_git_status
121
+ check_on_main_branch
122
+ run_tests
123
+ run_linter
124
+ end
125
+
126
+ def check_git_status
127
+ output = `git status --porcelain`.strip
128
+ unless output.empty?
129
+ error("Working directory is not clean. Commit or stash changes first.")
130
+ end
131
+ end
132
+
133
+ def check_on_main_branch
134
+ branch = `git rev-parse --abbrev-ref HEAD`.strip
135
+ unless branch == "main"
136
+ unless confirm("You are on branch '#{branch}', not 'main'. Continue anyway?")
137
+ error("Aborted. Switch to main branch or use --yes to override.")
138
+ end
139
+ end
140
+ end
141
+
142
+ def run_tests
143
+ step("Running tests")
144
+ unless system("bundle exec rake test > /dev/null 2>&1")
145
+ error("Tests failed! Fix tests before releasing.")
146
+ end
147
+ end
148
+
149
+ def run_linter
150
+ step("Running linter")
151
+ unless system("bundle exec rake standard > /dev/null 2>&1")
152
+ error("Linter failed! Fix linting issues before releasing.")
153
+ end
154
+ end
155
+
156
+ def check_gran_dependency
157
+ gemspec_path = GIBLISH_DIR / "giblish.gemspec"
158
+ content = File.read(gemspec_path)
159
+
160
+ if content =~ /add_runtime_dependency\s+"gran",\s+"~>\s*([\d.]+)"/
161
+ current_dep = $1
162
+ latest_gran = get_latest_rubygems_version("gran")
163
+
164
+ if latest_gran && Gem::Version.new(latest_gran) > Gem::Version.new(current_dep)
165
+ puts "⚠️ Gran dependency in giblish.gemspec is ~> #{current_dep}"
166
+ puts " Latest gran on RubyGems is #{latest_gran}"
167
+ puts " Consider updating the dependency before releasing."
168
+ unless confirm("Continue with current gran dependency?")
169
+ error("Aborted. Update gran dependency first.")
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ # Version Management
176
+ def bump_version(version_file)
177
+ current_version = extract_version(version_file)
178
+ new_version = calculate_new_version(current_version)
179
+
180
+ puts "Current version: #{current_version}"
181
+ puts "New version: #{new_version}"
182
+
183
+ unless confirm("Bump version to #{new_version}?")
184
+ error("Aborted by user")
185
+ end
186
+
187
+ update_version_file(version_file, current_version, new_version)
188
+ new_version
189
+ end
190
+
191
+ def extract_version(version_file)
192
+ content = File.read(version_file)
193
+ if content =~ /VERSION\s*=\s*"([\d.]+)"/
194
+ $1
195
+ else
196
+ error("Could not find VERSION in #{version_file}")
197
+ end
198
+ end
199
+
200
+ def calculate_new_version(current)
201
+ return specific_version if specific_version
202
+
203
+ parts = current.split(".").map(&:to_i)
204
+ case version_type
205
+ when "major"
206
+ "#{parts[0] + 1}.0.0"
207
+ when "minor"
208
+ "#{parts[0]}.#{parts[1] + 1}.0"
209
+ when "patch"
210
+ "#{parts[0]}.#{parts[1]}.#{parts[2] + 1}"
211
+ end
212
+ end
213
+
214
+ def update_version_file(version_file, old_version, new_version)
215
+ content = File.read(version_file)
216
+ content.gsub!(/VERSION\s*=\s*"#{Regexp.escape(old_version)}"/,
217
+ "VERSION = \"#{new_version}\"")
218
+
219
+ if dry_run
220
+ puts "[DRY-RUN] Would update #{version_file.basename} to #{new_version}"
221
+ else
222
+ File.write(version_file, content)
223
+ puts "✓ Updated #{version_file.basename}"
224
+ end
225
+ end
226
+
227
+ # Changelog
228
+ def update_changelog(version)
229
+ step("Updating CHANGELOG")
230
+ puts "⚠️ Please update the CHANGELOG manually with changes for v#{version}"
231
+ puts "Press Enter when done..."
232
+ $stdin.gets unless yes
233
+ end
234
+
235
+ # Gem Operations
236
+ def build_gem
237
+ step("Building giblish gem")
238
+
239
+ if dry_run
240
+ puts "[DRY-RUN] Would run: gem build giblish.gemspec"
241
+ else
242
+ unless system("gem build giblish.gemspec")
243
+ error("Failed to build gem")
244
+ end
245
+ puts "✓ Gem built successfully"
246
+ end
247
+ end
248
+
249
+ def publish_gem
250
+ gem_file = Dir.glob("giblish-*.gem").max_by { |f| File.mtime(f) }
251
+
252
+ unless gem_file
253
+ error("No gem file found to publish")
254
+ end
255
+
256
+ step("Publishing #{gem_file}")
257
+
258
+ unless confirm("Push #{gem_file} to RubyGems.org?")
259
+ error("Aborted by user")
260
+ end
261
+
262
+ if dry_run
263
+ puts "[DRY-RUN] Would run: gem push #{gem_file}"
264
+ else
265
+ unless system("gem push #{gem_file}")
266
+ error("Failed to publish gem")
267
+ end
268
+ puts "✓ Gem published successfully"
269
+ end
270
+ end
271
+
272
+ # Git Operations
273
+ def create_git_commit_and_tag(version)
274
+ tag_name = "giblish-v#{version}"
275
+ commit_message = "Release giblish v#{version}"
276
+
277
+ step("Creating git commit and tag")
278
+
279
+ if dry_run
280
+ puts "[DRY-RUN] Would run:"
281
+ puts " git add ."
282
+ puts " git commit -m '#{commit_message}'"
283
+ puts " git tag -a #{tag_name} -m '#{commit_message}'"
284
+ else
285
+ system("git add .")
286
+ unless system("git commit -m '#{commit_message}'")
287
+ error("Failed to create commit")
288
+ end
289
+ unless system("git tag -a #{tag_name} -m '#{commit_message}'")
290
+ error("Failed to create tag")
291
+ end
292
+ puts "✓ Created commit and tag: #{tag_name}"
293
+ end
294
+ end
295
+
296
+ def push_to_github
297
+ step("Pushing to GitHub")
298
+
299
+ unless confirm("Push commits and tags to GitHub?")
300
+ error("Aborted by user")
301
+ end
302
+
303
+ if dry_run
304
+ puts "[DRY-RUN] Would run:"
305
+ puts " git push origin main"
306
+ puts " git push origin --tags"
307
+ else
308
+ unless system("git push origin main")
309
+ error("Failed to push commits")
310
+ end
311
+ unless system("git push origin --tags")
312
+ error("Failed to push tags")
313
+ end
314
+ puts "✓ Pushed to GitHub"
315
+ end
316
+ end
317
+
318
+ # Helpers
319
+ def get_latest_rubygems_version(gem_name)
320
+ output = `gem search ^#{gem_name}$ --remote 2>/dev/null`
321
+ if output =~ /#{gem_name}\s+\(([\d.]+)\)/
322
+ $1
323
+ end
324
+ end
325
+
326
+ def with_dir(dir)
327
+ original_dir = Dir.pwd
328
+ Dir.chdir(dir)
329
+ yield
330
+ ensure
331
+ Dir.chdir(original_dir)
332
+ end
333
+
334
+ def confirm(message)
335
+ return true if yes
336
+ print "#{message} (y/N): "
337
+ response = $stdin.gets.strip.downcase
338
+ response == "y" || response == "yes"
339
+ end
340
+
341
+ def step(message)
342
+ puts "\n→ #{message}..."
343
+ end
344
+
345
+ def success(message)
346
+ puts "\n✓ #{message}\n"
347
+ end
348
+
349
+ def error(message)
350
+ puts "\n✗ ERROR: #{message}\n"
351
+ exit 1
352
+ end
353
+ end
354
+
355
+ # Run the script
356
+ if __FILE__ == $0
357
+ if ARGV.empty?
358
+ puts "Usage: release.rb [options] COMMAND [VERSION]"
359
+ puts "Run with --help for more information"
360
+ exit 1
361
+ end
362
+
363
+ manager = GiblishReleaseManager.new(ARGV)
364
+ manager.run
365
+ end