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/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
|
+

|
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
|