pinpoint 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +181 -3
- data/lib/pinpoint/address.rb +10 -0
- data/lib/pinpoint/config/formats/us.yml +21 -0
- data/lib/pinpoint/{formats.rb → config/patterns.rb} +0 -0
- data/lib/pinpoint/{us_states.rb → config/us_states.rb} +0 -0
- data/lib/pinpoint/format.rb +61 -0
- data/lib/pinpoint/format/file.rb +45 -0
- data/lib/pinpoint/format/list.rb +53 -0
- data/lib/pinpoint/format/parse_error.rb +6 -0
- data/lib/pinpoint/format/parser.rb +94 -0
- data/lib/pinpoint/format/style.rb +81 -0
- data/lib/pinpoint/format/token.rb +47 -0
- data/lib/pinpoint/format/token_list.rb +49 -0
- data/lib/pinpoint/format/tokenizer.rb +172 -0
- data/lib/pinpoint/formatter.rb +86 -0
- data/lib/pinpoint/validations.rb +2 -2
- data/lib/pinpoint/version.rb +1 -1
- data/spec/address_spec.rb +4 -1
- data/spec/format/file_spec.rb +24 -0
- data/spec/format/list_spec.rb +24 -0
- data/spec/format/parser_spec.rb +60 -0
- data/spec/format/style_spec.rb +57 -0
- data/spec/format/token_set_spec.rb +43 -0
- data/spec/format/token_spec.rb +25 -0
- data/spec/format/tokenizer_spec.rb +78 -0
- data/spec/format_spec.rb +35 -0
- data/spec/formatter_spec.rb +112 -0
- data/spec/pinpoint_spec.rb +2 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/validations_spec.rb +4 -1
- metadata +52 -86
data/lib/pinpoint/version.rb
CHANGED
data/spec/address_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require '
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/address'
|
2
3
|
|
3
4
|
describe 'Pinpoint::Address' do
|
4
5
|
let(:address) { Pinpoint::Address.new }
|
@@ -7,6 +8,8 @@ describe 'Pinpoint::Address' do
|
|
7
8
|
it { address.respond_to?(:region=).should be_true }
|
8
9
|
it { address.respond_to?(:province).should be_true }
|
9
10
|
it { address.respond_to?(:province=).should be_true }
|
11
|
+
it { address.respond_to?(:locality).should be_true }
|
12
|
+
it { address.respond_to?(:locality=).should be_true }
|
10
13
|
it { address.respond_to?(:postal_code).should be_true }
|
11
14
|
it { address.respond_to?(:postal_code=).should be_true }
|
12
15
|
it { address.respond_to?(:postalcode).should be_true }
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/format/file'
|
3
|
+
|
4
|
+
describe Pinpoint::Format::File do
|
5
|
+
let(:file_class) { Pinpoint::Format::File }
|
6
|
+
|
7
|
+
it 'can retrieve Styles for a given country' do
|
8
|
+
File.should_receive(:read).and_return <<-FORMAT_YAML
|
9
|
+
one_line: 'foo'
|
10
|
+
multi_line: 'bar'
|
11
|
+
html: 'baz'
|
12
|
+
FORMAT_YAML
|
13
|
+
|
14
|
+
styles = file_class.styles_for(:us)
|
15
|
+
|
16
|
+
styles.keys.should eql [
|
17
|
+
:one_line,
|
18
|
+
:multi_line,
|
19
|
+
:html
|
20
|
+
]
|
21
|
+
|
22
|
+
styles.values.should be_all { |s| s.class == Pinpoint::Format::Style }
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/format/list'
|
3
|
+
|
4
|
+
describe Pinpoint::Format::List do
|
5
|
+
let(:format_list_class) { Pinpoint::Format::List }
|
6
|
+
let(:format_list) { format_list_class.new }
|
7
|
+
|
8
|
+
it 'can find formats for a country that it has not already loaded' do
|
9
|
+
Pinpoint::Format.should_receive(:lookup_by_country)
|
10
|
+
.with(:us)
|
11
|
+
|
12
|
+
format_list[:us]
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can memoize formats for performance' do
|
16
|
+
Pinpoint::Format.should_receive(:lookup_by_country)
|
17
|
+
.once
|
18
|
+
.with(:us)
|
19
|
+
.and_return('format')
|
20
|
+
|
21
|
+
format_list[:us]
|
22
|
+
format_list[:us]
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/format/parser'
|
3
|
+
|
4
|
+
describe Pinpoint::Format::Parser do
|
5
|
+
let(:parser_class) { Pinpoint::Format::Parser }
|
6
|
+
let(:parsable_string) { '(%s, )((%l, )(%p )%z)(, %c)' }
|
7
|
+
let(:parser) { parser_class.new(parsable_string) }
|
8
|
+
|
9
|
+
context 'when the parsable string contains literal parenthesis' do
|
10
|
+
let(:parsable_string) { '%)%((%((%))%)%%%))' }
|
11
|
+
|
12
|
+
it 'can properly parse a string' do
|
13
|
+
parser.parse.should eql [
|
14
|
+
')',
|
15
|
+
'(',
|
16
|
+
[
|
17
|
+
'(',
|
18
|
+
[
|
19
|
+
')'
|
20
|
+
],
|
21
|
+
')',
|
22
|
+
'%',
|
23
|
+
')'
|
24
|
+
]
|
25
|
+
]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'checks that the token set is valid before attempting to process it' do
|
30
|
+
parser.send(:tokens).should_receive :valid?
|
31
|
+
|
32
|
+
parser.parse
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when the parsable string is typical' do
|
36
|
+
it 'can properly parse a string' do
|
37
|
+
parser.parse.should eql [
|
38
|
+
[
|
39
|
+
:street,
|
40
|
+
', ',
|
41
|
+
],
|
42
|
+
[
|
43
|
+
[
|
44
|
+
:locality,
|
45
|
+
', '
|
46
|
+
],
|
47
|
+
[
|
48
|
+
:province,
|
49
|
+
' '
|
50
|
+
],
|
51
|
+
:postal_code
|
52
|
+
],
|
53
|
+
[
|
54
|
+
', ',
|
55
|
+
:country
|
56
|
+
]
|
57
|
+
]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/format/style'
|
3
|
+
|
4
|
+
describe Pinpoint::Format::Style do
|
5
|
+
let(:style_class) { Pinpoint::Format::Style }
|
6
|
+
|
7
|
+
it 'can instantiate itself based on a style definition' do
|
8
|
+
style_definition = '((%s, )(%l, ))(%p )%z(, %c)'
|
9
|
+
style = style_class.from_yaml style_definition
|
10
|
+
|
11
|
+
style.send(:structure).first.first.should eql [:street, ', ']
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can output an address based on a style' do
|
15
|
+
style_definition = '((%s, )(%l, ))(%p )%z(, %c)'
|
16
|
+
style = style_class.from_yaml style_definition
|
17
|
+
address = stub(street: 'a',
|
18
|
+
locality: 'b',
|
19
|
+
province: 'c',
|
20
|
+
postal_code: 'd',
|
21
|
+
country: 'e')
|
22
|
+
|
23
|
+
style.output(address).should eql 'a, b, c d, e'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can output an address based on a style with newlines' do
|
27
|
+
style_definition = "((%s\n)((%l, ))(%p )%z\n)(%c\n)"
|
28
|
+
style = style_class.from_yaml style_definition
|
29
|
+
address = stub(street: 'a',
|
30
|
+
locality: 'b',
|
31
|
+
province: 'c',
|
32
|
+
postal_code: 'd',
|
33
|
+
country: 'e')
|
34
|
+
|
35
|
+
style.output(address).should eql <<-MULTILINE
|
36
|
+
a
|
37
|
+
b, c d
|
38
|
+
e
|
39
|
+
MULTILINE
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'can safely output unsafe HTML' do
|
43
|
+
style_definition = "<span>%s</span>"
|
44
|
+
style = style_class.from_yaml style_definition
|
45
|
+
address = stub(:street => "<script>alert('Gotcha!');</script>")
|
46
|
+
|
47
|
+
style.output(address).should eql '<span><script>alert('Gotcha!');</script></span>'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'understands not to output a grouping without alphanumeric characters' do
|
51
|
+
style_definition = '(%s, )'
|
52
|
+
style = style_class.from_yaml style_definition
|
53
|
+
address = stub(street: '')
|
54
|
+
|
55
|
+
style.output(address).should eql ''
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/format/token_list'
|
3
|
+
|
4
|
+
describe Pinpoint::Format::TokenList do
|
5
|
+
let(:token_list_class) { Pinpoint::Format::TokenList }
|
6
|
+
let(:token_list) { token_list_class.new }
|
7
|
+
let(:group_start_token) { stub(:type => :group_start) }
|
8
|
+
let(:group_end_token) { stub(:type => :group_end) }
|
9
|
+
|
10
|
+
it 'is an Array' do; token_list.should be_an Array; end
|
11
|
+
|
12
|
+
it 'is valid if the number of group pair tokens is equal' do
|
13
|
+
token_list = token_list_class.new [group_start_token, group_end_token]
|
14
|
+
|
15
|
+
token_list.should be_valid
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'is not valid if the number of group pair tokens is unequal' do
|
19
|
+
token_list = token_list_class.new [group_start_token]
|
20
|
+
|
21
|
+
expect { token_list.valid? }.to raise_error Pinpoint::Format::UnevenNestingError
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'yields each token to the block when processing' do
|
25
|
+
result = ''
|
26
|
+
token_list = token_list_class.new [group_start_token, group_end_token]
|
27
|
+
|
28
|
+
token_list.process_each! do |token|
|
29
|
+
result << token.type.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
result.should eql 'group_startgroup_end'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'deletes itself as it processes' do
|
36
|
+
token_list = token_list_class.new [group_start_token]
|
37
|
+
|
38
|
+
token_list.process_each! do |token|
|
39
|
+
end
|
40
|
+
|
41
|
+
token_list.should be_empty
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/format/token'
|
3
|
+
|
4
|
+
describe Pinpoint::Format::Token do
|
5
|
+
it 'can be instantiated with the proper arguments' do
|
6
|
+
token = Pinpoint::Format::Token.new(:foo, 'bar')
|
7
|
+
|
8
|
+
token.type.should eql :foo
|
9
|
+
token.value.should eql 'bar'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'always has a symbol for a type even if instantiated with something else' do
|
13
|
+
token = Pinpoint::Format::Token.new('foo', 'bar')
|
14
|
+
|
15
|
+
token.type.should eql :foo
|
16
|
+
token.value.should eql 'bar'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can determine the value that is needed when processing the token' do
|
20
|
+
Pinpoint::Format::Token.new(:group_start).processed_value.should eql :group_start
|
21
|
+
Pinpoint::Format::Token.new(:group_end).processed_value.should eql :group_end
|
22
|
+
Pinpoint::Format::Token.new(:literal, 'foo').processed_value.should eql 'foo'
|
23
|
+
Pinpoint::Format::Token.new(:street).processed_value.should eql :street
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/format/tokenizer'
|
3
|
+
|
4
|
+
describe Pinpoint::Format::Tokenizer do
|
5
|
+
let(:tokenizer_class) { Pinpoint::Format::Tokenizer }
|
6
|
+
let(:tokenable) { '((%s), )(((%p) )(%z))(, (%c))' }
|
7
|
+
let(:tokenizer) { Pinpoint::Format::Tokenizer.new(tokenable) }
|
8
|
+
|
9
|
+
it 'can process a String into Tokens' do
|
10
|
+
tokenizer.to_token_list.map(&:to_ary).should eql [
|
11
|
+
[:group_start, '(' ],
|
12
|
+
[:group_start, '(' ],
|
13
|
+
[:street, '%s' ],
|
14
|
+
[:group_end, ')' ],
|
15
|
+
[:literal, ', ' ],
|
16
|
+
[:group_end, ')' ],
|
17
|
+
[:group_start, '(' ],
|
18
|
+
[:group_start, '(' ],
|
19
|
+
[:group_start, '(' ],
|
20
|
+
[:province, '%p' ],
|
21
|
+
[:group_end, ')' ],
|
22
|
+
[:literal, ' ' ],
|
23
|
+
[:group_end, ')' ],
|
24
|
+
[:group_start, '(' ],
|
25
|
+
[:postal_code, '%z' ],
|
26
|
+
[:group_end, ')' ],
|
27
|
+
[:group_end, ')' ],
|
28
|
+
[:group_start, '(' ],
|
29
|
+
[:literal, ', ' ],
|
30
|
+
[:group_start, '(' ],
|
31
|
+
[:country, '%c' ],
|
32
|
+
[:group_end, ')' ],
|
33
|
+
[:group_end, ')' ]
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when the String passed contains literal percent sign tokens' do
|
38
|
+
let(:tokenable) { '%%%c' }
|
39
|
+
|
40
|
+
it 'parses correctly' do
|
41
|
+
tokenizer.to_token_list.map(&:to_ary).should eql [
|
42
|
+
[:literal, '%' ],
|
43
|
+
[:country, '%c' ]
|
44
|
+
]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when the String passed contains an improper syntax' do
|
49
|
+
let(:tokenable) { '%%%c%io' }
|
50
|
+
|
51
|
+
it 'parses correctly' do
|
52
|
+
expect { tokenizer.to_token_list }.to raise_error(
|
53
|
+
Pinpoint::Format::ParseError,
|
54
|
+
"Cannot parse the remainder of the tokenable string: '%io'"
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when the String passed contains complex grouping and parenthesis literals' do
|
60
|
+
let(:tokenable) { '%)%((%((%))%)%%%))' }
|
61
|
+
|
62
|
+
it 'parses correctly' do
|
63
|
+
tokenizer.to_token_list.map(&:to_ary).should eql [
|
64
|
+
[:literal, ')' ],
|
65
|
+
[:literal, '(' ],
|
66
|
+
[:group_start, '(' ],
|
67
|
+
[:literal, '(' ],
|
68
|
+
[:group_start, '(' ],
|
69
|
+
[:literal, ')' ],
|
70
|
+
[:group_end, ')' ],
|
71
|
+
[:literal, ')' ],
|
72
|
+
[:literal, '%' ],
|
73
|
+
[:literal, ')' ],
|
74
|
+
[:group_end, ')' ]
|
75
|
+
]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/spec/format_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'pinpoint/format'
|
3
|
+
|
4
|
+
describe Pinpoint::Format do
|
5
|
+
let(:format_class) { Pinpoint::Format }
|
6
|
+
let(:format) { format_class.new }
|
7
|
+
|
8
|
+
it 'can lookup a format by country' do
|
9
|
+
Pinpoint::Format::File.should_receive(:styles_for)
|
10
|
+
.and_return 'styles'
|
11
|
+
|
12
|
+
format = format_class.lookup_by_country(:us)
|
13
|
+
|
14
|
+
format.should be_a format_class
|
15
|
+
format.styles.should eql 'styles'
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when it has one or more Styles' do
|
19
|
+
let(:style) { stub }
|
20
|
+
|
21
|
+
before { format.styles = { :one_line => style } }
|
22
|
+
|
23
|
+
it 'tells the relevant Style to output the address' do
|
24
|
+
style.should_receive(:output).with('address').and_return 'formatted_address'
|
25
|
+
|
26
|
+
format.output('address', :style => :one_line).should eql 'formatted_address'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'defaults output style to one-line' do
|
30
|
+
style.should_receive(:output).with('address').and_return 'formatted_address'
|
31
|
+
|
32
|
+
format.output('address').should eql 'formatted_address'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'rspectacular'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'pinpoint/formatter'
|
4
|
+
|
5
|
+
|
6
|
+
class Address < Struct.new( :name,
|
7
|
+
:street,
|
8
|
+
:locality,
|
9
|
+
:province,
|
10
|
+
:county,
|
11
|
+
:postal_code,
|
12
|
+
:country,
|
13
|
+
:latitude,
|
14
|
+
:longitude)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Pinpoint::Formatter do
|
18
|
+
let(:address) { Address.new 'Kwik-E-Mart',
|
19
|
+
'123 Apu Lane',
|
20
|
+
'Springfield',
|
21
|
+
'NW',
|
22
|
+
'Springfield County',
|
23
|
+
'12345',
|
24
|
+
'United States',
|
25
|
+
'12345',
|
26
|
+
'67890' }
|
27
|
+
|
28
|
+
describe '#format' do
|
29
|
+
it 'can format it in one line US format' do
|
30
|
+
expected = "Kwik-E-Mart, 123 Apu Lane, Springfield, NW 12345, United States"
|
31
|
+
|
32
|
+
Pinpoint::Formatter.format(address, country: :us,
|
33
|
+
style: :one_line_with_name)
|
34
|
+
.should eql(expected)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can format it in multiline US format' do
|
38
|
+
expected = <<-MULTILINE
|
39
|
+
Kwik-E-Mart
|
40
|
+
123 Apu Lane
|
41
|
+
Springfield, NW 12345
|
42
|
+
United States
|
43
|
+
MULTILINE
|
44
|
+
|
45
|
+
Pinpoint::Formatter.format(address, country: :us,
|
46
|
+
style: :multi_line_with_name)
|
47
|
+
.should eql(expected)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'can format it in HTML in US format' do
|
51
|
+
expected = <<-MULTILINE
|
52
|
+
<address>
|
53
|
+
<span class="section">
|
54
|
+
<span class="name">Kwik-E-Mart</span>
|
55
|
+
</span>
|
56
|
+
<span class="section">
|
57
|
+
<span class="street">123 Apu Lane</span>
|
58
|
+
</span>
|
59
|
+
<span class="section">
|
60
|
+
<span class="locality">Springfield</span>
|
61
|
+
<span class="province">NW</span>
|
62
|
+
<span class="postal_code">12345</span>
|
63
|
+
</span>
|
64
|
+
<span class="section">
|
65
|
+
<span class="country">United States</span>
|
66
|
+
</span>
|
67
|
+
</address>
|
68
|
+
MULTILINE
|
69
|
+
|
70
|
+
Pinpoint::Formatter.format(address, country: :us,
|
71
|
+
style: :html)
|
72
|
+
.should eql(expected)
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when the address contains unsafe characters' do
|
76
|
+
let(:address) { Address.new "<script>alert('Gotcha!');</script>",
|
77
|
+
'123 Apu Lane',
|
78
|
+
'Springfield',
|
79
|
+
'NW',
|
80
|
+
'Springfield County',
|
81
|
+
'12345',
|
82
|
+
'United States',
|
83
|
+
'12345',
|
84
|
+
'67890' }
|
85
|
+
|
86
|
+
it 'escapes anything that is unsafe' do
|
87
|
+
expected = <<-MULTILINE
|
88
|
+
<address>
|
89
|
+
<span class="section">
|
90
|
+
<span class="name"><script>alert('Gotcha!');</script></span>
|
91
|
+
</span>
|
92
|
+
<span class="section">
|
93
|
+
<span class="street">123 Apu Lane</span>
|
94
|
+
</span>
|
95
|
+
<span class="section">
|
96
|
+
<span class="locality">Springfield</span>
|
97
|
+
<span class="province">NW</span>
|
98
|
+
<span class="postal_code">12345</span>
|
99
|
+
</span>
|
100
|
+
<span class="section">
|
101
|
+
<span class="country">United States</span>
|
102
|
+
</span>
|
103
|
+
</address>
|
104
|
+
MULTILINE
|
105
|
+
|
106
|
+
Pinpoint::Formatter.format(address, country: :us,
|
107
|
+
style: :html)
|
108
|
+
.should eql(expected)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|