smartname 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,73 @@
1
+ class SmartName
2
+ module Manipulate
3
+ # replace a subname
4
+ # keys are used for comparison
5
+ def replace old, new
6
+ old_name = old.to_name
7
+ new_name = new.to_name
8
+ return self if old_name.length > length
9
+ return replace_part(old_name, new_name) if old_name.simple?
10
+ return self unless include? old_name
11
+ replace_all_subsequences(old_name, new_name).to_name
12
+ end
13
+
14
+ def replace_part oldpart, newpart
15
+ ensure_simpliness oldpart, "Use 'replace' to replace junctions"
16
+
17
+ oldpart = oldpart.to_name
18
+ newpart = newpart.to_name
19
+
20
+ parts.map do |p|
21
+ oldpart == p ? newpart : p
22
+ end.to_name
23
+ end
24
+
25
+ def replace_piece oldpiece, newpiece
26
+ oldpiece = oldpiece.to_name
27
+ newpiece = newpiece.to_name
28
+
29
+ return replace_part oldpiece, newpiece if oldpiece.simple?
30
+ return self unless self.starts_with?(oldpiece)
31
+ return newpiece if oldpiece.length == length
32
+ newpiece + self[oldpiece.length..-1]
33
+ end
34
+
35
+ private
36
+
37
+ def replace_all_subsequences oldseq, newseq
38
+ res = []
39
+ i = 0
40
+ while i <= length - oldseq.length
41
+ # for performance reasons: check first character first then the rest
42
+ if oldseq.part_keys.first == part_keys[i] &&
43
+ oldseq.part_keys == part_keys[i, oldseq.length]
44
+ res += newseq.parts
45
+ i += oldseq.length
46
+ else
47
+ res << parts[i]
48
+ i += 1
49
+ end
50
+ end
51
+ res += parts[i..-1] if i < length
52
+ res
53
+ end
54
+
55
+ def ensure_simpliness part, msg=nil
56
+ return if part.to_name.simple?
57
+ raise StandardError, "'#{part}' has to be simple. #{msg}"
58
+ end
59
+ end
60
+
61
+ # ~~~~~~~~~~~~~~~~~~~~ MISC ~~~~~~~~~~~~~~~~~~~~
62
+
63
+ # HACK. This doesn't belong here.
64
+ # shouldn't it use inclusions???
65
+ def self.substitute! str, hash
66
+ hash.keys.each do |var|
67
+ str.gsub! var_re do |x|
68
+ hash[var.to_sym]
69
+ end
70
+ end
71
+ str
72
+ end
73
+ end
@@ -0,0 +1,124 @@
1
+ class SmartName
2
+ # naming conventions:
3
+ # methods that end with _name return name objects
4
+ # the same methods without _name return strings
5
+ module Parts
6
+ attr_reader :parts, :part_keys, :simple
7
+
8
+ alias simple? simple
9
+ alias_method :to_a, :parts
10
+ alias_method :to_ary, :parts
11
+
12
+ def to_ary
13
+ if parts.empty?
14
+ [""]
15
+ else
16
+ parts
17
+ end
18
+ end
19
+
20
+ def initialize_parts
21
+ # -1 = don't suppress trailing null fields
22
+ @parts = @s.split(/\s*#{JOINT_RE}\s*/, -1)
23
+ @simple = @parts.size <= 1
24
+ # simple check needed to avoid inifinite recursion
25
+ @part_keys =
26
+ @simple ? [simple_key] : @parts.map { |p| p.to_name.simple_key }
27
+ end
28
+
29
+ def left
30
+ @left ||= simple? ? nil : parts[0..-2] * self.class.joint
31
+ end
32
+
33
+ def right
34
+ @right ||= simple? ? nil : parts[-1]
35
+ end
36
+
37
+ def left_name
38
+ @left_name ||= left && self.class.new(left)
39
+ end
40
+
41
+ def right_name
42
+ @right_name ||= right && self.class.new(right)
43
+ end
44
+
45
+ def left_key
46
+ @left_key ||= simple? ? nil : part_keys[0..-2] * self.class.joint
47
+ end
48
+
49
+ def right_key
50
+ @right_key ||= simple? ? nil : part_keys.last
51
+ end
52
+
53
+ def parents
54
+ @parents ||= junction? ? [left, right] : []
55
+ end
56
+
57
+ def parent_names
58
+ @parent_names ||= junction? ? [left_name, right_name] : []
59
+ end
60
+
61
+ def parent_keys
62
+ @parent_keys ||= junction? ? [left_key, right_key] : []
63
+ end
64
+
65
+ # Note that all names have a trunk and tag,
66
+ # but only junctions have left and right
67
+
68
+ def trunk
69
+ @trunk ||= simple? ? s : left
70
+ end
71
+
72
+ def tag
73
+ @tag ||= simple? ? s : right
74
+ end
75
+
76
+ def trunk_name
77
+ @trunk_name ||= simple? ? self : left_name
78
+ end
79
+
80
+ def tag_name
81
+ @tag_name ||= simple? ? self : right_name
82
+ end
83
+
84
+ def part_names
85
+ @part_names ||= parts.map(&:to_name)
86
+ end
87
+
88
+ def piece_names
89
+ @piece_names ||= pieces.map(&:to_name)
90
+ end
91
+
92
+ # self and all ancestors (= parts and recursive lefts)
93
+ # @example
94
+ # "A+B+C+D".to_name.pieces
95
+ # # => ["A", "B", "C", "D", "A+B", "A+B+C", "A+B+C+D"]
96
+ def pieces
97
+ @pieces ||=
98
+ if simple?
99
+ [self]
100
+ else
101
+ junction_pieces = []
102
+ parts[1..-1].inject parts[0] do |left, right|
103
+ piece = [left, right] * self.class.joint
104
+ junction_pieces << piece
105
+ piece
106
+ end
107
+ parts + junction_pieces
108
+ end
109
+ end
110
+
111
+ # name parts can be accessed and manipulated like an array
112
+ def method_missing method, *args, &block
113
+ if parts.respond_to?(method)
114
+ self.class.new parts.send(method, *args, &block)
115
+ else
116
+ super
117
+ end
118
+ end
119
+
120
+ def respond_to? method, include_private=false
121
+ super || parts.respond_to?(method, include_private)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,39 @@
1
+ class SmartName
2
+ module Predicates
3
+ # @return true if name has more than one part
4
+ def junction?
5
+ !simple?
6
+ end
7
+
8
+ def blank?
9
+ s.blank?
10
+ end
11
+ alias empty? blank?
12
+
13
+ def valid?
14
+ !parts.find do |pt|
15
+ pt.match self.class.banned_re
16
+ end
17
+ end
18
+
19
+ # @return true if name starts with the same parts as `prefix`
20
+ def starts_with? prefix
21
+ start_name = prefix.to_name
22
+ start_name == self[0, start_name.length]
23
+ end
24
+ alias_method :start_with?, :starts_with?
25
+
26
+ # @return true if name ends with the same parts as `prefix`
27
+ def ends_with? postfix
28
+ end_name = postfix.to_name
29
+ end_name == self[-end_name.length..-1]
30
+ end
31
+ alias_method :end_with?, :ends_with?
32
+
33
+ # @return true if name has a chain of parts that equals `subname`
34
+ def include? subname
35
+ subkey = subname.to_name.key
36
+ key =~ /(^|#{JOINT_RE})#{Regexp.quote subkey}($|#{JOINT_RE})/
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ class SmartName
2
+ module Variants
3
+ def simple_key
4
+ return "" if @s.empty?
5
+ decoded
6
+ .underscore
7
+ .gsub(/[^#{OK4KEY_RE}]+/, '_')
8
+ .split(/_+/)
9
+ .reject(&:empty?)
10
+ .map { |key| SmartName.stable_uninflect(key) }
11
+ .join('_')
12
+ end
13
+
14
+ def url_key
15
+ @url_key ||= part_names.map do |part_name|
16
+ stripped = part_name.decoded.gsub(/[^#{OK4KEY_RE}]+/, ' ').strip
17
+ stripped.gsub(/[\s\_]+/, '_')
18
+ end * self.class.joint
19
+ end
20
+
21
+ # safe to be used in HTML as id ('*' and '+' are not allowed),
22
+ # but the key is no longer unique.
23
+ # For example "A-XB" and "A+*B" have the same safe_key
24
+ def safe_key
25
+ @safe_key ||= key.tr('*', 'X').tr self.class.joint, '-'
26
+ end
27
+
28
+ def decoded
29
+ @decoded ||= s.index('&') ? HTMLEntities.new.decode(s) : s
30
+ end
31
+
32
+ def to_sym
33
+ s.to_sym
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,133 @@
1
+ # encoding: utf-8
2
+ require_relative "../../spec_helper"
3
+
4
+ RSpec.describe SmartName::Contextual do
5
+ describe '#to_absolute' do
6
+ it 'handles _self, _whole, _' do
7
+ expect('_self'.to_name.to_absolute('foo')).to eq('foo')
8
+ expect('_whole'.to_name.to_absolute('foo')).to eq('foo')
9
+ expect('_'.to_name.to_absolute('foo')).to eq('foo')
10
+ end
11
+
12
+ it 'handles _left' do
13
+ expect('_left+Z'.to_name.to_absolute('A+B+C')).to eq('A+B+Z')
14
+ end
15
+
16
+ it 'handles white space' do
17
+ expect('_left + Z'.to_name.to_absolute('A+B+C')).to eq('A+B+Z')
18
+ end
19
+
20
+ it 'handles _right' do
21
+ expect('_right+bang'.to_name.to_absolute('nutter+butter')).to eq('butter+bang')
22
+ expect('C+_right'.to_name.to_absolute('B+A')).to eq('C+A')
23
+ end
24
+
25
+ it 'handles leading +' do
26
+ expect('+bug'.to_name.to_absolute('hum')).to eq('hum+bug')
27
+ end
28
+
29
+ it 'handles trailing +' do
30
+ expect('bug+'.to_name.to_absolute('tracks')).to eq('bug+tracks')
31
+ end
32
+
33
+ it "handles leading + in context" do
34
+ expect("+B".to_name.to_absolute("+A")).to eq("+A+B")
35
+ end
36
+
37
+ it "handles leading + in context for child" do
38
+ expect("+A+B".to_name.to_absolute("+A")).to eq("+A+B")
39
+ end
40
+
41
+ it 'handles _(numbers)' do
42
+ expect('_1'.to_name.to_absolute('A+B+C')).to eq('A')
43
+ expect('_1+_2'.to_name.to_absolute('A+B+C')).to eq('A+B')
44
+ expect('_2+_3'.to_name.to_absolute('A+B+C')).to eq('B+C')
45
+ end
46
+
47
+ it 'handles _LLR etc' do
48
+ expect('_R'.to_name.to_absolute('A+B+C+D+E')).to eq('E')
49
+ expect('_L'.to_name.to_absolute('A+B+C+D+E')).to eq('A+B+C+D')
50
+ expect('_LR'.to_name.to_absolute('A+B+C+D+E')).to eq('D')
51
+ expect('_LL'.to_name.to_absolute('A+B+C+D+E')).to eq('A+B+C')
52
+ expect('_LLR'.to_name.to_absolute('A+B+C+D+E')).to eq('C')
53
+ expect('_LLL'.to_name.to_absolute('A+B+C+D+E')).to eq('A+B')
54
+ expect('_LLLR'.to_name.to_absolute('A+B+C+D+E')).to eq('B')
55
+ expect('_LLLL'.to_name.to_absolute('A+B+C+D+E')).to eq('A')
56
+ end
57
+
58
+ context 'mismatched requests' do
59
+ it 'returns _self for _left or _right on simple cards' do
60
+ expect('_left+Z'.to_name.to_absolute('A')).to eq('A+Z')
61
+ expect('_right+Z'.to_name.to_absolute('A')).to eq('A+Z')
62
+ end
63
+
64
+ it 'handles bogus numbers' do
65
+ expect('_1'.to_name.to_absolute('A')).to eq('A')
66
+ expect('_1+_2'.to_name.to_absolute('A')).to eq('A+A')
67
+ expect('_2+_3'.to_name.to_absolute('A')).to eq('A+A')
68
+ end
69
+
70
+ it 'handles bogus _llr requests' do
71
+ expect('_R'.to_name.to_absolute('A')).to eq('A')
72
+ expect('_L'.to_name.to_absolute('A')).to eq('A')
73
+ expect('_LR'.to_name.to_absolute('A')).to eq('A')
74
+ expect('_LL'.to_name.to_absolute('A')).to eq('A')
75
+ expect('_LLR'.to_name.to_absolute('A')).to eq('A')
76
+ expect('_LLL'.to_name.to_absolute('A')).to eq('A')
77
+ expect('_LLLR'.to_name.to_absolute('A')).to eq('A')
78
+ expect('_LLLL'.to_name.to_absolute('A')).to eq('A')
79
+ end
80
+ end
81
+ end
82
+
83
+ describe '#to_show' do
84
+ it 'ignores ignorables' do
85
+ expect('you+awe'.to_name.to_show('you')).to eq('+awe')
86
+ expect('me+you+awe'.to_name.to_show('you')).to eq('me+awe') #HMMM..... what should this do?
87
+ expect('me+you+awe'.to_name.to_show('me' )).to eq('+you+awe')
88
+ expect('me+you+awe'.to_name.to_show('me','you')).to eq('+awe')
89
+ expect('me+you'.to_name.to_show('me','you')).to eq('me+you')
90
+ expect('?a?+awe'.to_name.to_show('A')).to eq('+awe')
91
+ expect('+awe'.to_name.to_show()).to eq('+awe')
92
+ expect('+awe'.to_name.to_show(nil)).to eq('+awe')
93
+ end
94
+ end
95
+
96
+ describe "#child_of?" do
97
+ [["A+B", "A", true],
98
+ ["A+B", "B", true],
99
+ ["A+B+C", "A+B", true],
100
+ ["A+B+C", "C", true],
101
+ ["A+B", "A+B", false],
102
+ ["A+B", "A+B+C", false],
103
+ ["A", "A", false],
104
+ ["A+B+C", "A", false],
105
+ ["A+C", "A+B", false],
106
+ ["A+B", "C+B", false],
107
+ ["X+A+B", "A+C", false],
108
+ ["+A", "B", true],
109
+ ["+A", "A", true],
110
+ ["+A", "+D", true],
111
+ ["+A", "+A", false]].each do |a, b, res|
112
+ it "#{a} is a child of #{b}" do
113
+ expect(a.to_name.child_of?(b)).to be res
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "#relative_name" do
119
+ [["A+B", "A", "+B"],
120
+ ["A+B", "B", "A"],
121
+ ["A", "A", "A"],
122
+ ["A+B", "A+B", "A+B"],
123
+ ["A", "A+B", "A"],
124
+ ["A+C", "A+B", "+C"],
125
+ ["A+B", "C+B", "A"],
126
+ ["X+A+B", "A+C", "X+B"]].each do |name, context, res|
127
+ it "#{name} relative to #{context} is #{res}" do
128
+ expect(name.to_name.relative_name(context).to_s).to eq res
129
+ end
130
+ end
131
+ end
132
+
133
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+ require_relative "../../spec_helper"
3
+
4
+ RSpec.describe SmartName::Manipulate do
5
+ describe '#replace' do
6
+ def replace str, change
7
+ str.to_name.replace(*change.to_a.flatten).to_s
8
+ end
9
+
10
+ it 'replaces first name part' do
11
+ expect(replace 'a+b', 'a' => 'x').to eq('x+b')
12
+ end
13
+ it 'replaces last name part' do
14
+ expect(replace 'a+b', 'b' => 'x').to eq('a+x')
15
+ end
16
+ it 'replaces middle name part' do
17
+ expect(replace 'a+c+b', 'c' => 'x').to eq('a+x+b')
18
+ end
19
+ it 'replaces all occurences' do
20
+ expect(replace'a+c+b+c+c','c' => 'x').to eq('a+x+b+x+x')
21
+ end
22
+ it 'replaces junction' do
23
+ expect(replace'a+b+c', 'a+b' => 'x').to eq('x+c')
24
+ expect(replace'a+b+c+d', 'a+b' => 'e+f').to eq('e+f+c+d')
25
+ end
26
+ it "replaces two part tag" do
27
+ expect(replace'a+b+c','b+c' => 'x').to eq('a+x')
28
+ end
29
+ it "replaces whole name" do
30
+ expect(replace'a+b+c','a+b+c' => 'x').to eq('x')
31
+ end
32
+
33
+ it "replaces based on key match" do
34
+ expect(replace'A+ b +C?','a+b+c' => 'x').to eq('x')
35
+ end
36
+ it "replaces with original format" do
37
+ expect(replace'a+b','a+B' => 'X?+C').to eq('X?+C')
38
+ end
39
+ end
40
+
41
+ describe '#replace_part' do
42
+ def replace str, change
43
+ str.to_name.replace_part(*change.to_a.flatten).to_s
44
+ end
45
+ it 'replaces all occurences' do
46
+ expect(replace'a+c+b+c+c','c' => 'x').to eq('a+x+b+x+x')
47
+ end
48
+ context "first argument is not a part" do
49
+ it 'raises error' do
50
+ expect { replace('a+c+b','a+c' => 'x') }.to raise_error(StandardError, /has to be simple/)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#replace_piece' do
56
+ def replace str, change
57
+ str.to_name.replace_piece(*change.to_a.flatten).to_s
58
+ end
59
+ it "replaces two part trunk" do
60
+ expect(replace('a+b+c', 'a+b' =>'x')).to eq('x+c')
61
+ end
62
+ it "doesn't replace two part tag" do
63
+ expect(replace('a+b+c', 'b+c' => 'x')).to eq('a+b+c')
64
+ end
65
+ it "replaces whole name" do
66
+ expect(replace'a+b+c','a+b+c' => 'x').to eq('x')
67
+ end
68
+ it "replaces based on key match" do
69
+ expect(replace'A+ b +C?','a+b+c' => 'x').to eq('x')
70
+ end
71
+ it "replaces with original format" do
72
+ expect(replace'a+b','a+B' => 'X?+C').to eq('X?+C')
73
+ end
74
+ end
75
+ end