Class: Kettle::Dev::VersionBump

Inherits:
Object
  • Object
show all
Defined in:
lib/kettle/dev/version_bump.rb

Overview

Reusable engine behind kettle-bump.

Constant Summary collapse

BUMP_TYPES =
%w[major minor patch pre].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root:, target_version:, current_version: nil, from_version: nil) ⇒ VersionBump

Returns a new instance of VersionBump.



129
130
131
132
133
134
# File 'lib/kettle/dev/version_bump.rb', line 129

def initialize(root:, target_version:, current_version: nil, from_version: nil)
  @root = root
  @current_version = current_version || Kettle::Dev::Versioning.detect_version(root)
  @target_version = self.class.resolve_target_version(target_version, @current_version)
  @from_version = self.class.validate_version(from_version) if from_version
end

Instance Attribute Details

#current_versionObject (readonly)

Returns the value of attribute current_version.



136
137
138
# File 'lib/kettle/dev/version_bump.rb', line 136

def current_version
  @current_version
end

#from_versionObject (readonly)

Returns the value of attribute from_version.



136
137
138
# File 'lib/kettle/dev/version_bump.rb', line 136

def from_version
  @from_version
end

#rootObject (readonly)

Returns the value of attribute root.



136
137
138
# File 'lib/kettle/dev/version_bump.rb', line 136

def root
  @root
end

#target_versionObject (readonly)

Returns the value of attribute target_version.



136
137
138
# File 'lib/kettle/dev/version_bump.rb', line 136

def target_version
  @target_version
end

Class Method Details

.bumped_prerelease_version(current_version) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/kettle/dev/version_bump.rb', line 45

def bumped_prerelease_version(current_version)
  version = Gem::Version.new(current_version)
  segments = version.segments
  prerelease_index = segments.index { |segment| !segment.is_a?(Integer) }
  unless prerelease_index
    raise Kettle::Dev::Error, "cannot pre-bump version without prerelease segment #{current_version.inspect}"
  end

  release_core = segments[0...prerelease_index].join(".")
  prerelease_suffix = prerelease_suffix_for(current_version, release_core)
  "#{release_core}.#{prerelease_suffix.next}"
end

.bumped_version(type, current_version) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/kettle/dev/version_bump.rb', line 25

def bumped_version(type, current_version)
  return bumped_prerelease_version(current_version) if type == "pre"

  version = Gem::Version.new(current_version)
  segments = version.segments
  unless segments.all? { |segment| segment.is_a?(Integer) }
    raise Kettle::Dev::Error, "cannot #{type}-bump non-numeric version #{current_version.inspect}"
  end

  major, minor, patch = (segments + [0, 0, 0])[0, 3]
  case type
  when "major"
    "#{major + 1}.0.0"
  when "minor"
    "#{major}.#{minor + 1}.0"
  when "patch"
    "#{major}.#{minor}.#{patch + 1}"
  end
end

.each_node(root) ⇒ Object



98
99
100
101
102
103
104
105
106
107
# File 'lib/kettle/dev/version_bump.rb', line 98

def each_node(root)
  return enum_for(__method__, root) unless block_given?

  queue = [root]
  until queue.empty?
    node = queue.shift
    yield node
    queue.concat(node.child_nodes.compact) if node.respond_to?(:child_nodes)
  end
end

.file_edit(path, source, start_offset, end_offset, replacement) ⇒ Object



109
110
111
# File 'lib/kettle/dev/version_bump.rb', line 109

def file_edit(path, source, start_offset, end_offset, replacement)
  {path: path, source: source, start_offset: start_offset, end_offset: end_offset, replacement: replacement}
end

.parse_source(source, path) ⇒ Object

Raises:



82
83
84
85
86
87
88
# File 'lib/kettle/dev/version_bump.rb', line 82

def parse_source(source, path)
  require_prism
  parse_result = Prism.parse(source)
  raise Kettle::Dev::Error, "could not parse #{path}" unless parse_result.success?

  parse_result
end

.prerelease_suffix_for(current_version, release_core) ⇒ Object

Raises:



58
59
60
61
62
63
64
65
66
# File 'lib/kettle/dev/version_bump.rb', line 58

def prerelease_suffix_for(current_version, release_core)
  prefix = "#{release_core}."
  return string_tail(current_version, prefix.length) if current_version.start_with?(prefix)

  canonical_version = Gem::Version.new(current_version).to_s
  return string_tail(canonical_version, prefix.length) if canonical_version.start_with?(prefix)

  raise Kettle::Dev::Error, "cannot find prerelease segment in version #{current_version.inspect}"
end

.quote_like(original, value) ⇒ Object



113
114
115
116
# File 'lib/kettle/dev/version_bump.rb', line 113

def quote_like(original, value)
  quote = original.start_with?("'") ? "'" : '"'
  "#{quote}#{value}#{quote}"
end

.require_prismObject



90
91
92
93
94
95
96
# File 'lib/kettle/dev/version_bump.rb', line 90

def require_prism
  return if defined?(Prism)

  require "prism"
rescue LoadError => error
  raise Kettle::Dev::Error, "kettle-bump requires Prism; install the prism gem or run on Ruby 3.3+ (#{error.message})"
end

.resolve_target_version(target, current_version) ⇒ Object



10
11
12
13
14
15
16
17
# File 'lib/kettle/dev/version_bump.rb', line 10

def resolve_target_version(target, current_version)
  target = target.to_s
  if BUMP_TYPES.include?(target)
    bumped_version(target, current_version)
  else
    validate_version(target)
  end
end

.string_tail(value, offset) ⇒ Object



68
69
70
# File 'lib/kettle/dev/version_bump.rb', line 68

def string_tail(value, offset)
  value[offset, value.length - offset]
end

.validate_version(version) ⇒ Object



19
20
21
22
23
# File 'lib/kettle/dev/version_bump.rb', line 19

def validate_version(version)
  Gem::Version.new(version).to_s
rescue ArgumentError => error
  raise Kettle::Dev::Error, "invalid version #{version.inspect}: #{error.message}"
end

.version_string_node(source, path) ⇒ Object

Raises:



72
73
74
75
76
77
78
79
80
# File 'lib/kettle/dev/version_bump.rb', line 72

def version_string_node(source, path)
  parse_result = parse_source(source, path)
  constant = each_node(parse_result.value).find do |node|
    node.is_a?(Prism::ConstantWriteNode) && node.name == :VERSION && node.value.is_a?(Prism::StringNode)
  end
  raise Kettle::Dev::Error, "could not find string VERSION constant in #{path}" unless constant

  constant.value
end

.write_edits(edits) ⇒ Object



118
119
120
121
122
123
124
125
126
# File 'lib/kettle/dev/version_bump.rb', line 118

def write_edits(edits)
  edits.group_by { |edit| edit.fetch(:path) }.each_value do |file_edits|
    source = file_edits.first.fetch(:source)
    file_edits.sort_by { |edit| -edit.fetch(:start_offset) }.each do |edit|
      source[edit.fetch(:start_offset)...edit.fetch(:end_offset)] = edit.fetch(:replacement)
    end
    File.write(file_edits.first.fetch(:path), source)
  end
end

Instance Method Details

#editsObject



138
139
140
141
# File 'lib/kettle/dev/version_bump.rb', line 138

def edits
  validate_from_version
  version_file_edits + gemspec_version_edits
end

#write!Object



143
144
145
# File 'lib/kettle/dev/version_bump.rb', line 143

def write!
  self.class.write_edits(edits)
end