@@ -4,7 +4,8 @@ class Template
4
4
5
5
# @param [String] template A Template is a coded string of the form Prefix.Mask that governs how identifiers will be minted.
6
6
def initialize template
7
- @template = template
7
+ @template = template
8
+ parse_template
8
9
end
9
10
10
11
def mint n
@@ -15,63 +16,69 @@ def mint n
15
16
str
16
17
end
17
18
18
- def valid? str
19
- return false unless str [ 0 ..prefix . length ] == prefix
20
-
19
+ # A noid has the structure (prefix)(code)(checkdigit)
20
+ # the regexp has the following named captures
21
+ # :body - the prefix and the code
22
+ # :code - the changing id characters (not the prefix and not the checkdigit)
23
+ # :check - the checkdigit, if there is one. This field is missing if there is no checkdigit
24
+ def validation_regexp
25
+ return @validation_regexp if @validation_regexp
26
+ pattern_list = [ '\A' , '(?<body>' , Regexp . escape ( prefix ) , '(?<code>' ]
21
27
if generator == 'z'
22
- str [ prefix . length ..-1 ] . length > 2
23
- else
24
- str [ prefix . length ..-1 ] . length == characters . length
28
+ pattern_list << character_to_pattern ( @character_list . last ) << '*'
25
29
end
30
+ @character_list . each do |c |
31
+ pattern_list << character_to_pattern ( c )
32
+ end
33
+ pattern_list << ')' << ')' # close <code> and <body>
34
+ if checkdigit?
35
+ pattern_list << '(?<check>' << character_to_pattern ( 'e' ) << ')'
36
+ end
37
+ pattern_list << '\Z'
26
38
27
- characters . split ( '' ) . each_with_index do |c , i |
28
- case c
29
- when 'e'
30
- return false unless Noid ::XDIGIT . include? str [ prefix . length + i ]
31
- when 'd'
32
- return false unless str [ prefix . length + i ] =~ /\d /
33
- end
34
- end
35
-
36
- return false unless checkdigit ( str [ 0 ..-2 ] ) == str . split ( '' ) . last if checkdigit?
39
+ @validation_regexp = Regexp . new ( pattern_list . join ( '' ) )
40
+ end
37
41
42
+ ##
43
+ # Is the passed in string valid against this template?
44
+ # Also validates the check digit, if the template has one
45
+ def valid? ( str )
46
+ match = validation_regexp . match ( str )
47
+ return false if match . nil?
48
+ if checkdigit?
49
+ return checkdigit ( match [ :body ] ) == match [ :check ]
50
+ end
38
51
true
39
52
end
40
53
41
54
##
42
55
# identifier prefix string
43
56
def prefix
44
- @prefix ||= @template . split ( '.' ) . first
57
+ @prefix
45
58
end
46
59
47
60
##
48
61
# identifier mask string
49
62
def mask
50
- @mask ||= @template . split ( '.' ) . last
63
+ @mask
51
64
end
52
65
53
66
##
54
67
# generator type to use: r, s, z
55
68
def generator
56
- @generator ||= mask [ 0 .. 0 ]
69
+ @generator
57
70
end
58
71
59
72
##
60
73
# sequence pattern: e (extended), d (digit)
61
74
def characters
62
- @characters ||= begin
63
- if checkdigit?
64
- mask [ 1 ..-2 ]
65
- else
66
- mask [ 1 ..-1 ]
67
- end
68
- end
75
+ @characters
69
76
end
70
77
71
78
##
72
79
# should generated identifiers have a checkdigit?
73
80
def checkdigit?
74
- mask . split ( '' ) . last == 'k'
81
+ @checkdigit
75
82
end
76
83
77
84
##
@@ -91,18 +98,50 @@ def min
91
98
##
92
99
# maximum sequence value for the template
93
100
def max
94
- @max ||= begin
95
- case generator
96
- when 'z'
97
- nil
98
- else
99
- characters . split ( '' ) . map { |x | character_space ( x ) } . compact . inject ( 1 ) { |total , x | total *= x }
100
- end
101
- end
101
+ @max ||= case generator
102
+ when 'z' then nil
103
+ else size_list . inject ( 1 ) { |total , x | total * x }
104
+ end
102
105
end
103
106
104
107
105
108
protected
109
+ ##
110
+ # parse @template and put the results into class variables
111
+ # raise an exception if there is a parse error
112
+ #
113
+ def parse_template
114
+ match = /\A (?<prefix>.*)\. (?<generator>[rsz])(?<mask>[ed]+)(?<check>k?)\Z / . match ( @template )
115
+ if match . nil?
116
+ raise "Malformed Noid template '#{ @template } '"
117
+ end
118
+ @prefix = match [ :prefix ]
119
+ @generator = match [ :generator ]
120
+ @characters = match [ :mask ]
121
+ @character_list = @characters . split ( '' )
122
+ @mask = @generator + @characters
123
+ @checkdigit = ( match [ :check ] == 'k' )
124
+ end
125
+
126
+ def xdigit_pattern
127
+ @xdigit_pattern ||= "[" + Noid ::XDIGIT . join ( '' ) + "]"
128
+ end
129
+
130
+ def character_to_pattern ( c )
131
+ case c
132
+ when 'e' then xdigit_pattern
133
+ when 'd' then '\d'
134
+ else ''
135
+ end
136
+ end
137
+
138
+ ##
139
+ # Return a list giving the number of possible characters at each position
140
+ #
141
+ def size_list
142
+ @size_list ||= @character_list . map { |c | character_space ( c ) }
143
+ end
144
+
106
145
##
107
146
# total size of a given template character value
108
147
# @param [String] c
@@ -119,18 +158,18 @@ def character_space c
119
158
# convert a minter position to a noid string under this template
120
159
# @param [Integer] n
121
160
# @return [String]
122
- def n2xdig n
123
- xdig = characters . reverse . split ( '' ) . map do |c |
124
- value = n % character_space ( c )
125
- n = n / character_space ( c )
161
+ def n2xdig ( n )
162
+ xdig = size_list . reverse . map do |size |
163
+ value = n % size
164
+ n = n / size
126
165
Noid ::XDIGIT [ value ]
127
166
end . compact . join ( '' )
128
167
129
168
if generator == 'z'
130
- c = characters . split ( '' ) . last
169
+ size = size_list . last
131
170
while n > 0
132
- value = n % character_space ( c )
133
- n = n / character_space ( c )
171
+ value = n % size
172
+ n = n / size
134
173
xdig += Noid ::XDIGIT [ value ]
135
174
end
136
175
end
0 commit comments