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 CHANGED
@@ -1,4 +1,182 @@
1
- pinpoint
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
- (Un)conventional Address Composition for Ruby (and Rails)
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 &copy; 2012 Chirrpy. It is free software, and may be redistributed under the terms specified in the LICENSE file.
@@ -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,6 @@
1
+ module Pinpoint
2
+ class Format
3
+ class ParseError < StandardError; end
4
+ class UnevenNestingError < ParseError; end
5
+ end
6
+ 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