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.
- checksums.yaml +4 -4
- data/.gitignore +20 -53
- data/.solargraph.yml +15 -0
- data/Changelog.adoc +16 -0
- data/README.adoc +1 -1
- data/diagram-classes.png +0 -0
- data/giblish.gemspec +10 -22
- data/lib/giblish/application.rb +11 -57
- data/lib/giblish/config_builders/docid_config_builder.rb +84 -0
- data/lib/giblish/config_builders/git_index_config_builder.rb +37 -0
- data/lib/giblish/config_builders/index_config_builder.rb +53 -0
- data/lib/giblish/config_utils.rb +0 -42
- data/lib/giblish/configurator.rb +81 -163
- data/lib/giblish/docid/docid.rb +3 -3
- data/lib/giblish/gitrepos/checkoutmanager.rb +3 -2
- data/lib/giblish/gitrepos/history_pb.rb +10 -5
- data/lib/giblish/indexbuilders/depgraphbuilder.rb +1 -1
- data/lib/giblish/indexbuilders/subtree_indices.rb +5 -11
- data/lib/giblish/indexbuilders/verbatimtree.rb +1 -1
- data/lib/giblish/layout_config/html_layout_config.rb +77 -0
- data/lib/giblish/layout_config/layout_config_result.rb +35 -0
- data/lib/giblish/layout_config/pdf_layout_config.rb +89 -0
- data/lib/giblish/node_data_provider.rb +137 -0
- data/lib/giblish/resourcepaths.rb +3 -2
- data/lib/giblish/search/searchquery.rb +3 -3
- data/lib/giblish/search/textsearcher.rb +3 -3
- data/lib/giblish/subtreeinfobuilder.rb +2 -2
- data/lib/giblish/treeconverter.rb +13 -8
- data/lib/giblish/version.rb +1 -1
- data/scripts/release.rb +365 -0
- metadata +37 -178
- data/.github/workflows/unit_tests.yml +0 -30
- data/.ruby-version +0 -1
- data/lib/giblish/pathtree.rb +0 -518
|
@@ -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,
|
|
36
|
+
def method_missing(meth, ...)
|
|
37
37
|
unless respond_to_missing?(meth)
|
|
38
|
-
super
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
data/lib/giblish/version.rb
CHANGED
data/scripts/release.rb
ADDED
|
@@ -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
|