Skip to content

Instantly share code, notes, and snippets.

@jpignata
Last active June 6, 2020 14:03
Show Gist options
  • Save jpignata/64174893960753ee2200ad24ed16ef0b to your computer and use it in GitHub Desktop.
Save jpignata/64174893960753ee2200ad24ed16ef0b to your computer and use it in GitHub Desktop.
class Version
module Symbol
POSITIVE_INTEGER = 0
ZERO = 1
LETTER = 2
DOT = 3
DASH = 4
UNKNOWN = 99
def self.for(character)
case character
when ("1".."9")
Symbol::POSITIVE_INTEGER
when "0"
Symbol::ZERO
when ("A".."Z"), ("a".."z")
Symbol::LETTER
when "."
Symbol::DOT
when "-"
Symbol::DASH
else
Symbol::UNKNOWN
end
end
end
class Acceptor
STATES = {
:start => {
Symbol::POSITIVE_INTEGER => :major,
Symbol::ZERO => :major_end,
},
:major => {
Symbol::POSITIVE_INTEGER => :major,
Symbol::ZERO => :major,
Symbol::DOT => :minor_start,
Symbol::DASH => :metadata
},
:major_end => {
Symbol::DOT => :minor_start,
Symbol::DASH => :metadata
},
:minor_start => {
Symbol::POSITIVE_INTEGER => :minor,
Symbol::ZERO => :minor_end
},
:minor => {
Symbol::POSITIVE_INTEGER => :minor,
Symbol::ZERO => :minor,
Symbol::DOT => :patch_start,
Symbol::DASH => :metadata
},
:minor_end => {
Symbol::DOT => :patch_start,
Symbol::DASH => :metadata
},
:patch_start => {
Symbol::POSITIVE_INTEGER => :patch,
Symbol::ZERO => :patch_end
},
:patch => {
Symbol::POSITIVE_INTEGER => :patch,
Symbol::ZERO => :patch,
Symbol::DASH => :metadata
},
:patch_end => {
Symbol::DASH => :metadata
},
:metadata => {
Symbol::POSITIVE_INTEGER => :metadata,
Symbol::ZERO => :metadata,
Symbol::LETTER => :metadata,
Symbol::DOT => :metadata
},
}
ACCEPTING = [:major, :minor, :patch, :major_end, :minor_end, :patch_end, :metadata]
def initialize(candidate)
if !candidate.respond_to?(:each_char)
raise ArgumentError, "Invalid argument: #{candidate}"
end
@candidate = candidate
end
def valid?
state = :start
symbols = candidate.each_char.map { |character| Symbol.for(character) }
return false if symbols.include?(Symbol::UNKNOWN)
symbols.each do |symbol|
if STATES[state][symbol]
state = STATES[state][symbol]
else
return false
end
end
ACCEPTING.include?(state)
end
private
attr_reader :candidate
end
end
require 'spec_helper'
require 'version/acceptor'
describe Version::Acceptor do
TABLE = [
['1.2.13', true],
['1.0.0', true],
['1.0.1', true],
['1.1.0', true],
['1.11.1', true],
['1.1.12', true],
['1.21.1', true],
['1.1.222', true],
['1.0.0-preview.0', true],
['0', true],
['0.0', true],
['0.0.0', true],
['0.0.1', true],
['0.1.1', true],
['', false, 'an empty string'],
['tetris', false, 'a word'],
['1.0.0.0', false],
['0.0.0.0', false],
['1.0.%', false]
]
TABLE.each do |candidate, valid, display|
context(display || candidate) do
it do
expectation = expect(Version::Acceptor.new(candidate))
if valid
expectation.to be_valid
else
expectation.not_to be_valid
end
end
end
end
it "raises ArgumentError if a string is not provided" do
expect { Version::Acceptor.new([]) }.to raise_error(ArgumentError)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment