lib/uuid/ncname.rb in uuid-ncname-0.2.5 vs lib/uuid/ncname.rb in uuid-ncname-0.2.6
- old
+ new
@@ -114,12 +114,13 @@
list.pack 'N4'
},
],
]
- def self.encode_version version
- ((version & 15) + 65).chr
+ def self.encode_version version, radix
+ offset = radix == 32 ? 97 : 65
+ ((version & 15) + offset).chr
end
def self.decode_version version
(version.upcase.ord - 65) % 16
end
@@ -135,10 +136,31 @@
version
end
public
+ # This error gets thrown when a UUID-NCName token can't be
+ # positively determined to be one version or the other.
+ class AmbiguousToken < ArgumentError
+
+ # @return [String] The ambiguous token
+ attr_reader :token
+ # @return [String] The UUID when decoded using version 0
+ attr_reader :v0
+ # @return [String] The UUID when decoded using version 1
+ attr_reader :v1
+
+ # @param token [#to_s] The token in question
+ # @param v0 [#to_s] UUID decoded with decoding scheme version 0
+ # @param v1 [#to_s] UUID decoded with decoding scheme version 1
+
+ def initialize token, v0: nil, v1: nil
+ @v0 = v0 || from_ncname(token, version: 0)
+ @v1 = v1 || from_ncname(token, version: 1)
+ end
+ end
+
# Converts a UUID (or object that when converted to a string looks
# like a UUID) to an NCName. By default it produces the Base64
# variant.
#
# @param uuid [#to_s] whatever it is, it had better look like a
@@ -146,11 +168,11 @@
# 16-byte binary strings, etc.
#
# @param radix [32, 64] either the number 32 or the number 64.
#
# @param version [0, 1] An optional formatting version, where 0 is
- # the naïve original version and 1 moves the `variant` nybble out
+ # the naïve original version and 1 moves the +variant+ nybble out
# to the end of the identifier. You will be warned for the time
# being if you do not set this parameter explicitly. The default
# version is 1.
#
# @param align [true, false] Optional directive to treat the
@@ -158,21 +180,22 @@
# representation. Since the version nybble is removed from the
# string and the first 120 bits divide evenly into both Base32 and
# Base64, the overhang is only ever 4 bits. This means that when
# the terminating character is aligned, it will always be in the
# range of the letters A through P in (the RFC 3548/4648
- # representations of) both Base32 and Base64. When `version` is 1
+ # representations of) both Base32 and Base64. When +version+ is 1
# and the terminating character is aligned, RFC4122-compliant UUIDs
- # will always terminate with I, J, K, or L. Defaults to `true`.
+ # will always terminate with +I+, +J+, +K+, or +L+. Defaults to
+ # +true+.
#
# @return [String] The NCName-formatted UUID.
def self.to_ncname uuid, radix: 64, version: nil, align: true
raise 'Radix must be either 32 or 64' unless [32, 64].include? radix
raise 'UUID must be something stringable' if uuid.nil? or
not uuid.respond_to? :to_s
- raise 'Align must be true or false' unless [true, false].include? align
+ align = !!align # coerce to a boolean
# XXX remove this when appropriate
version = warn_version(version)
uuid = uuid.to_s
@@ -193,13 +216,13 @@
end
raise 'Binary representation of UUID is shorter than 16 bytes' if
bin.length < 16
- uuidver, content = TRANSFORM[version][0].call bin[0, 16]
+ uuidver, content = TRANSFORM[version].first.call bin[0, 16]
- encode_version(uuidver) + ENCODE[radix].call(content, align)
+ encode_version(uuidver, radix) + ENCODE[radix].call(content, align)
end
# Converts an NCName-encoded UUID back to its canonical
# representation. Will return nil if the input doesn't match the
# radix (if supplied) or is otherwise malformed.
@@ -208,20 +231,20 @@
# 22-character (Base64) variant, or a 26-character (Base32) variant.
#
# @param radix [nil, 32, 64] Optional radix; will use heuristic if omitted.
#
# @param format [:str, :hex, :b64, :bin] An optional formatting
- # parameter; defaults to `:str`, the canonical string representation.
+ # parameter; defaults to +:str+, the canonical string representation.
#
# @param version [0, 1] See ::to_ncname. Defaults to 1.
#
# @param align [nil, true, false] See ::to_ncname for details.
- # Setting this parameter to `nil`, the default, will cause the
+ # Setting this parameter to +nil+, the default, will cause the
# decoder to detect the alignment state from the identifier.
#
- # @param validate [false, true] Check that the ninth octet is
- # correctly masked _after_ decoding.
+ # @param validate [false, true] Check that the ninth (the variant)
+ # octet is correctly masked _after_ decoding.
#
# @return [String, nil] The corresponding UUID or nil if the input
# is malformed.
def self.from_ncname ncname,
@@ -335,36 +358,42 @@
# Test if the given token is a UUID NCName, with a hint to its
# version. This method can positively identify a token as a UUID
# NCName, but there is a small subset of UUIDs which will produce
# tokens which are valid in both versions. The method returns
- # `false` if the token is invalid, otherwise it returns `0` or `1`
+ # +false+ if the token is invalid, otherwise it returns +0+ or +1+
# for the guessed version.
#
- # @note Version 1 tokens always end with I, J, K, or L (with base32
- # being case-insensitive), so tokens that end in something else will
- # be version 0.
+ # @note Version 1 tokens always end with +I+, +J+, +K+, or +L+ (with
+ # base32 being case-insensitive), so tokens that end in something
+ # else will always be version 0.
#
# @param token [#to_s] The token to test
#
+ # @param strict [false, true]
+ #
# @return [false, 0, 1]
- def self.valid? token
+ def self.valid? token, strict: false
token = token.to_s
if /^[A-Pa-p](?:[0-9A-Za-z_-]{21}|[2-7A-Za-z]{25})$/.match token
# false is definitely version zero but true is only maybe version 1
version = /^(?:.{21}[I-L]|.{25}[I-Li-l])$/.match(token) ? 1 : 0
# try decoding with validation on
uu = from_ncname token, version: version, validate: true
- if version == 1 and !uu
- # try version zero
- uu = from_ncname token, version: 0, validate: true
- # either zero or invalid
- uu ? 0 : false
- else
- version
+ # note that version 1 will always return something because the
+ # method of detecting it is a version 1 also happens to be the
+ # method of determining whether or not it is valid.
+ return false unless uu
+
+ if version == 1 and strict
+ # but we can also check if the input is a valid version 0
+ u0 = from_ncname token, version: 0, validate: true
+ raise AmbiguousToken.new(token, v0: u0, v1: uu) if u0
end
+
+ version
else
false
end
end