pinpoint 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/README.md
CHANGED
@@ -1,4 +1,182 @@
|
|
1
|
-
|
2
|
-
========
|
1
|
+
<img align="right" width="400" src="http://cache.jezebel.com/assets/images/7/2009/07/custom_1245192016735_wargames_missle_locations.jpg" />
|
3
2
|
|
4
|
-
|
3
|
+
Pinpoint
|
4
|
+
===============================================================================
|
5
|
+
|
6
|
+
_(Un)conventional Address Composition for Ruby (and Rails)_
|
7
|
+
|
8
|
+
Supported Rubies
|
9
|
+
--------------------------------
|
10
|
+
* MRI Ruby 1.9.2
|
11
|
+
* MRI Ruby 1.9.3
|
12
|
+
* JRuby (in 1.9 compat mode)
|
13
|
+
|
14
|
+
<br/>
|
15
|
+
<br/>
|
16
|
+
|
17
|
+
Installation
|
18
|
+
-------------------------------------------------------------------------------
|
19
|
+
|
20
|
+
First:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem install pinpoint
|
24
|
+
```
|
25
|
+
|
26
|
+
Then in your script:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require 'pinpoint'
|
30
|
+
```
|
31
|
+
|
32
|
+
or in your Gemfile
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem 'pinpoint'
|
36
|
+
```
|
37
|
+
|
38
|
+
or from IRB
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
irb -r 'pinpoint'
|
42
|
+
```
|
43
|
+
|
44
|
+
Usage
|
45
|
+
-------------------------------------------------------------------------------
|
46
|
+
|
47
|
+
### Direct ####################################################################
|
48
|
+
|
49
|
+
Typically Pinpoint is used by requiring it and using the `Pinpoint::Address`
|
50
|
+
class directly. Like so:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class ThingWithAddress
|
54
|
+
attr_accessor :address
|
55
|
+
end
|
56
|
+
|
57
|
+
thing = ThingWithAddress.new
|
58
|
+
thing.address = Pinpoint::Address.new(:foo => :bar)
|
59
|
+
```
|
60
|
+
|
61
|
+
### Composable ################################################################
|
62
|
+
|
63
|
+
However it can also be included in the class when the class already has
|
64
|
+
accessors for all of the different address attributes to generate a composed
|
65
|
+
address.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class ThingWithAddress
|
69
|
+
include Pinpoint::Composable
|
70
|
+
|
71
|
+
attr_accessor :street
|
72
|
+
attr_accessor :city
|
73
|
+
attr_accessor :state
|
74
|
+
attr_accessor :zip_code
|
75
|
+
attr_accessor :country
|
76
|
+
|
77
|
+
pinpoint :address
|
78
|
+
end
|
79
|
+
|
80
|
+
thing = ThingWithAddress.new
|
81
|
+
|
82
|
+
# Set all address fields on `thing`
|
83
|
+
|
84
|
+
thing.address # => Pinpoint::Address
|
85
|
+
```
|
86
|
+
|
87
|
+
#### Field Prefixes
|
88
|
+
|
89
|
+
If your class has fields that are prefixed by a string (eg venue_city,
|
90
|
+
venue_state, etc) you can pass `field_prefix` to the compose class method like
|
91
|
+
so:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class ThingWithAddress
|
95
|
+
# attr_accessors like:
|
96
|
+
attr_accessor :venue_street
|
97
|
+
attr_accessor :venue_city
|
98
|
+
|
99
|
+
pinpoint :venue, :field_prefix => 'venue'
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
### Address Formatting ########################################################
|
104
|
+
|
105
|
+
Pinpoint has an advanced address formatter which can output an address in
|
106
|
+
multiple country formats.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
pinpoint_address.to_s(country: :us,
|
110
|
+
style: :one_line)
|
111
|
+
# => 'Kwik-E-Mart, 123 Apu Lane, Springfield, NW 12345, United States'
|
112
|
+
|
113
|
+
pinpoint\_address.to\_s(country: :ru,
|
114
|
+
style: :one_line)
|
115
|
+
# => 'Kwik-E-Mart, ul. Apu Lane d. 123, pos. Springfield, NW obl, 12345, United States'
|
116
|
+
```
|
117
|
+
|
118
|
+
_Note: By default, Pinpoint will format addresses for the country that the
|
119
|
+
address is located in._
|
120
|
+
|
121
|
+
### Retrieving Address Information ############################################
|
122
|
+
|
123
|
+
* `:name` - The name of the person or business this address is associated with
|
124
|
+
* `:location_details` - Free form but typically describes the department, mail
|
125
|
+
stop, etc).
|
126
|
+
* `:street` - The name of the street
|
127
|
+
* `:street_number` - The number that corresponds to the place that the address
|
128
|
+
is associated with.
|
129
|
+
* `:suite_number` - The number associated with the aparment, office or suite.
|
130
|
+
* `:zip_code` - The postal code associated with the address
|
131
|
+
* `:city` - The town/villiage/city associated with the address
|
132
|
+
* `:state` - The state/department/province associated with the address
|
133
|
+
* `:country` - The country associated with the address
|
134
|
+
|
135
|
+
#### Aliases ####
|
136
|
+
|
137
|
+
Some of the above attributes are aliased to some other common names:
|
138
|
+
|
139
|
+
* `:name` => `:recipient`
|
140
|
+
* `:name` => `:building_name`
|
141
|
+
* `:location_details` => `:mail_stop`
|
142
|
+
* `:zip_code` => `:postal_code`
|
143
|
+
* `:city` => `:locality`
|
144
|
+
* `:state` => `:province`
|
145
|
+
|
146
|
+
### Geocoding #################################################################
|
147
|
+
|
148
|
+
By default, Pinpoint uses [Geocoder]() to add latitude and longitude information
|
149
|
+
to addresses.
|
150
|
+
|
151
|
+
If a Pinpoint address is composed into your class, you can add a `:latitude` and
|
152
|
+
`:longitude` attribute to it and it will be set/modified when the address is
|
153
|
+
changed.
|
154
|
+
|
155
|
+
Road Map
|
156
|
+
--------------------------------
|
157
|
+
|
158
|
+
* Advanced parsing support
|
159
|
+
* SimpleForm inputs
|
160
|
+
|
161
|
+
Issues
|
162
|
+
--------------------------------
|
163
|
+
|
164
|
+
If you have problems, please create a [Github issue](https://github.com/chirrpy/pinpoint/issues).
|
165
|
+
|
166
|
+
Credits
|
167
|
+
--------------------------------
|
168
|
+
|
169
|
+
![](https://dl.dropbox.com/s/f9s2qd0kmbc8nwl/github_logo.png?dl=1)
|
170
|
+
|
171
|
+
pinpoint is maintained by [Chrrpy, LLC](http://chirrpy.com)
|
172
|
+
|
173
|
+
The names and logos for Chirrpy are trademarks of Chrrpy, LLC
|
174
|
+
|
175
|
+
Contributors
|
176
|
+
--------------------------------
|
177
|
+
* [Jeff Felchner](https://github.com/jfelchner)
|
178
|
+
|
179
|
+
License
|
180
|
+
--------------------------------
|
181
|
+
|
182
|
+
pinpoint is Copyright © 2012 Chirrpy. It is free software, and may be redistributed under the terms specified in the LICENSE file.
|
data/lib/pinpoint/address.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'pinpoint/formatter'
|
2
|
+
|
1
3
|
module Pinpoint
|
2
4
|
class Address
|
3
5
|
attr_accessor :name,
|
@@ -10,6 +12,10 @@ module Pinpoint
|
|
10
12
|
:latitude,
|
11
13
|
:longitude
|
12
14
|
|
15
|
+
# City Aliases
|
16
|
+
alias :locality :city
|
17
|
+
alias :locality= :city=
|
18
|
+
|
13
19
|
# State Aliases
|
14
20
|
alias :region :state
|
15
21
|
alias :region= :state=
|
@@ -52,6 +58,10 @@ module Pinpoint
|
|
52
58
|
blank?(zip_code)
|
53
59
|
end
|
54
60
|
|
61
|
+
def to_s(options = { :country => :us, :format => :one_line })
|
62
|
+
Formatter.format(self, options)
|
63
|
+
end
|
64
|
+
|
55
65
|
private
|
56
66
|
def present?(value)
|
57
67
|
!blank?(value)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
one_line_with_name: '(%n, )(%s, )(%l, )(%p )%z(, %c)'
|
2
|
+
one_line: '(%s, )(%l, )(%p )%z(, %c)'
|
3
|
+
multi_line: "(%s\n)((%l, )(%p )%z\n)(%c\n)"
|
4
|
+
multi_line_with_name: "(%n\n)(%s\n)((%l, )(%p )%z\n)(%c\n)"
|
5
|
+
html: |
|
6
|
+
<address>
|
7
|
+
<span class="section">
|
8
|
+
<span class="name">%n</span>
|
9
|
+
</span>
|
10
|
+
<span class="section">
|
11
|
+
<span class="street">%s</span>
|
12
|
+
</span>
|
13
|
+
<span class="section">
|
14
|
+
<span class="locality">%l</span>
|
15
|
+
<span class="province">%p</span>
|
16
|
+
<span class="postal_code">%z</span>
|
17
|
+
</span>
|
18
|
+
<span class="section">
|
19
|
+
<span class="country">%c</span>
|
20
|
+
</span>
|
21
|
+
</address>
|
File without changes
|
File without changes
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'pinpoint/format/file'
|
2
|
+
|
3
|
+
module Pinpoint
|
4
|
+
class Format
|
5
|
+
attr_accessor :styles
|
6
|
+
|
7
|
+
##
|
8
|
+
# Public: Initialize an empty Format with no styles.
|
9
|
+
#
|
10
|
+
def initialize
|
11
|
+
styles = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Public: Attempts to find a format for a given country.
|
16
|
+
#
|
17
|
+
# country - A Symbol representing the lowercased two-character [ISO
|
18
|
+
# 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) country code for
|
19
|
+
# the country you are trying to load a format for.
|
20
|
+
#
|
21
|
+
# Example
|
22
|
+
#
|
23
|
+
# lookup_by_country(:us)
|
24
|
+
# # => <Format styles: {one_line: <Format::Style>, ....}>
|
25
|
+
#
|
26
|
+
# Returns a Format loaded with all of the styles that it is capable of.
|
27
|
+
#
|
28
|
+
def self.lookup_by_country(country)
|
29
|
+
format = self.new
|
30
|
+
format.styles = Pinpoint::Format::File.styles_for(country)
|
31
|
+
format
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Public: Will output any given address for the country defined in the
|
36
|
+
# Format.
|
37
|
+
#
|
38
|
+
# By default it will output in a 'one-line' style.
|
39
|
+
#
|
40
|
+
# address - The address that will be formatted (typically
|
41
|
+
# a Pinpoint::Address).
|
42
|
+
#
|
43
|
+
# options - A Hash of options for the method
|
44
|
+
#
|
45
|
+
# :style - The style to be applied to the address output
|
46
|
+
# (defaults to :one_line).
|
47
|
+
#
|
48
|
+
# Example
|
49
|
+
#
|
50
|
+
# output my_address, :style => :one_line
|
51
|
+
# # => 'Kwik-E-Mart, 123 Apu Lane, Springfield, NW 12345, United States'
|
52
|
+
#
|
53
|
+
# Returns a String representing the address in the specified style.
|
54
|
+
#
|
55
|
+
def output(address, options = {})
|
56
|
+
requested_style = options.fetch(:style, :one_line).to_sym
|
57
|
+
|
58
|
+
styles[requested_style].output(address)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pinpoint/format/style'
|
3
|
+
|
4
|
+
module Pinpoint
|
5
|
+
class Format
|
6
|
+
class File
|
7
|
+
|
8
|
+
##
|
9
|
+
# Public: Loads the format for the given country from the appropriate
|
10
|
+
# YAML file.
|
11
|
+
#
|
12
|
+
# It then converts the parsed YAML into Pinpoint::Format::Style objects which
|
13
|
+
# can be used to style something that quaks like an Address.
|
14
|
+
#
|
15
|
+
# country - A Symbol representing the lowercased two-character [ISO
|
16
|
+
# 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) country code for
|
17
|
+
# the country you are trying to load a format for.
|
18
|
+
#
|
19
|
+
# Returns a Hash containing symbolized keys for the style names and values
|
20
|
+
# containing Styles.
|
21
|
+
#
|
22
|
+
def self.styles_for(country)
|
23
|
+
raw_style_data(country).each_with_object({}) do |style_definition, hash|
|
24
|
+
style_name = style_definition[0]
|
25
|
+
style = style_definition[1]
|
26
|
+
|
27
|
+
hash[style_name.to_sym] = Format::Style.from_yaml(style)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.format_yaml_contents(country)
|
34
|
+
relative_path = "../../config/formats/#{country}.yml"
|
35
|
+
filename = ::File.expand_path(relative_path, __FILE__)
|
36
|
+
|
37
|
+
::File.read(filename)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.raw_style_data(country)
|
41
|
+
YAML::load(format_yaml_contents(country))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'pinpoint/format'
|
2
|
+
|
3
|
+
module Pinpoint
|
4
|
+
class Format
|
5
|
+
class List
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
##
|
9
|
+
# Public: Initializes a new empty List
|
10
|
+
#
|
11
|
+
def initialize
|
12
|
+
self.formats = Hash.new
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Public: Retrieves a Format for the given country.
|
17
|
+
#
|
18
|
+
# If the country's Format has already been retrieved, it is returned,
|
19
|
+
# otherwise it is looked up.
|
20
|
+
#
|
21
|
+
# country - The two letter ISO_3166-1 code for the country you're looking
|
22
|
+
# up the format for.
|
23
|
+
#
|
24
|
+
# Example
|
25
|
+
#
|
26
|
+
# [:us]
|
27
|
+
# # => <Format>
|
28
|
+
#
|
29
|
+
# Returns a Format which corresponds to the given country.
|
30
|
+
#
|
31
|
+
def [](country)
|
32
|
+
country = country.to_sym
|
33
|
+
|
34
|
+
get(country) ||
|
35
|
+
set(country, Pinpoint::Format.lookup_by_country(country))
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
attr_accessor :formats
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def get(country)
|
45
|
+
self.formats[country]
|
46
|
+
end
|
47
|
+
|
48
|
+
def set(country, format)
|
49
|
+
self.formats[country] = format
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'pinpoint/format/tokenizer'
|
2
|
+
|
3
|
+
###
|
4
|
+
# Private: Parses a set of Tokens into a parse tree that can be navigated by
|
5
|
+
# a Style to render some output.
|
6
|
+
#
|
7
|
+
module Pinpoint
|
8
|
+
class Format
|
9
|
+
class Parser
|
10
|
+
|
11
|
+
##
|
12
|
+
# Public: Initializes a Parser and converts the passed String into
|
13
|
+
# a TokenList that will be utilized when it is parsed.
|
14
|
+
#
|
15
|
+
# Raises a ParseError if the Tokenizer cannot tokenize the String
|
16
|
+
# Returns a Parser
|
17
|
+
#
|
18
|
+
def initialize(string)
|
19
|
+
self.tokens = Pinpoint::Format::Tokenizer.new(string).to_token_list
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Public: Provides a way to convert a TokenList into a tree repersenting
|
24
|
+
# groups, message names, and String literals.
|
25
|
+
#
|
26
|
+
# If the TokenList is not valid, a form of ParseError is raised.
|
27
|
+
#
|
28
|
+
# Example
|
29
|
+
#
|
30
|
+
# Parser.new('(%s, )((%l, )(%p )%z)(, %c)').parse
|
31
|
+
# # => [
|
32
|
+
# # [
|
33
|
+
# # :street,
|
34
|
+
# # ', ',
|
35
|
+
# # ],
|
36
|
+
# # [
|
37
|
+
# # [
|
38
|
+
# # :locality,
|
39
|
+
# # ', '
|
40
|
+
# # ],
|
41
|
+
# # [
|
42
|
+
# # :province,
|
43
|
+
# # ' '
|
44
|
+
# # ],
|
45
|
+
# # :postal_code
|
46
|
+
# # ],
|
47
|
+
# # [
|
48
|
+
# # ', ',
|
49
|
+
# # :country
|
50
|
+
# # ]
|
51
|
+
# # ]
|
52
|
+
#
|
53
|
+
# Returns an Array containing the parse tree structure
|
54
|
+
#
|
55
|
+
def parse
|
56
|
+
process(tokens) if tokens.valid?
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
##
|
62
|
+
# Protected: Reads the TokenList associated with the Parser
|
63
|
+
#
|
64
|
+
attr_accessor :tokens
|
65
|
+
|
66
|
+
##
|
67
|
+
# Protected: Can recursively process the TokenList into an Array containing
|
68
|
+
# the parse tree.
|
69
|
+
#
|
70
|
+
# See also Parser#parse
|
71
|
+
#
|
72
|
+
# Returns an Array containing the parse tree structure
|
73
|
+
#
|
74
|
+
def process(token_list)
|
75
|
+
result = []
|
76
|
+
|
77
|
+
token_list.process_each! do |token|
|
78
|
+
case token.processed_value
|
79
|
+
when :group_start
|
80
|
+
token_list, intermediate_result = process(token_list)
|
81
|
+
|
82
|
+
result << intermediate_result
|
83
|
+
when :group_end
|
84
|
+
return [token_list, result]
|
85
|
+
else
|
86
|
+
result << token.processed_value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
result
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|