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.
@@ -0,0 +1,81 @@
1
+ require 'pinpoint/format/parser'
2
+ require 'active_support/core_ext/string/output_safety'
3
+
4
+ module Pinpoint
5
+ class Format
6
+ class Style
7
+
8
+ ##
9
+ # Public: Processes the style information gleaned from the Pinpoint YAML
10
+ # format and generates a Style from it.
11
+ #
12
+ # style - The style information from the Pinpoint YAML format file. For
13
+ # example:
14
+ #
15
+ # (%s, )(%l, )(%p )%z(, %c)
16
+ #
17
+ # Returns a Pinpoint::Format::Style based on the information passed in.
18
+ #
19
+ def self.from_yaml(style_definition)
20
+ style = self.new
21
+ style.send(:structure=, Format::Parser.new(style_definition).parse)
22
+ style
23
+ end
24
+
25
+ ##
26
+ # Public: Can take an address-like object and output it in the style
27
+ # specified by the structure.
28
+ #
29
+ # Example
30
+ #
31
+ # # Assuming address is an address-like object and Style was
32
+ # # instantiated with '(%s, )(%l, )(%p )%z(, %c)'
33
+ # output address
34
+ # # => '123 First Street, Nashville, TN 37033, United States'
35
+ #
36
+ def output(address)
37
+ process(structure, address)
38
+ end
39
+
40
+ protected
41
+
42
+ ##
43
+ # Protected: Access to the underlying structure of the Style (typically is
44
+ # a result of calling Pinpoint::Format::Parser#parse.
45
+ #
46
+ attr_accessor :structure
47
+
48
+ private
49
+
50
+ ##
51
+ # Private: Can take a Style structure and assemble a String from data from
52
+ # a context.
53
+ #
54
+ # When finishing with a grouping, if there is no alphanumeric content
55
+ # within a grouping, it will be discarded.
56
+ #
57
+ # Returns a String containing data from the context and String literals
58
+ #
59
+ def process(structure, context)
60
+ processed = "".html_safe
61
+
62
+ structure.each_with_object(processed) do |token, result|
63
+ result << case token
64
+ when Array
65
+ process(token, context)
66
+ when Symbol
67
+ context.public_send(token)
68
+ when String
69
+ token.html_safe
70
+ end
71
+ end
72
+
73
+ processed.match(no_alphanumerics) ? "".html_safe : processed
74
+ end
75
+
76
+ def no_alphanumerics
77
+ /\A[^A-Za-z0-9]*\z/
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,47 @@
1
+ require 'ostruct'
2
+
3
+ module Pinpoint
4
+ class Format
5
+ class Token < Struct.new(:type, :value)
6
+ def initialize(*args)
7
+ args[0] = args[0].to_sym
8
+
9
+ super
10
+ end
11
+
12
+ def processed_value
13
+ case type
14
+ when :group_start
15
+ :group_start
16
+ when :group_end
17
+ :group_end
18
+ when :literal
19
+ value
20
+ else
21
+ message
22
+ end
23
+ end
24
+
25
+ def to_ary
26
+ [type, value]
27
+ end
28
+
29
+ private
30
+
31
+ def message
32
+ type_to_message_map[type]
33
+ end
34
+
35
+ def type_to_message_map
36
+ {
37
+ name: :name,
38
+ street: :street,
39
+ locality: :locality,
40
+ province: :province,
41
+ postal_code: :postal_code,
42
+ country: :country
43
+ }
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,49 @@
1
+ require 'pinpoint/format/parse_error'
2
+
3
+ module Pinpoint
4
+ class Format
5
+ class TokenList < Array
6
+
7
+ ##
8
+ # Public: Processes each item in the list by removing it and passing it to
9
+ # the block.
10
+ #
11
+ # At the end of the call, the list will be empty.
12
+ #
13
+ # Yields the Token of the iteration
14
+ #
15
+ # Returns nothing
16
+ #
17
+ def process_each!
18
+ while size > 0 do
19
+ token = delete_at(0)
20
+
21
+ yield token
22
+ end
23
+ end
24
+
25
+ ##
26
+ # Public: Verifies that the tokens in the list are in good form.
27
+ #
28
+ # Returns TrueClass if the tokens in the list are valid
29
+ # Raises Pinpoint::Format::UnevenNestingError if the number of
30
+ # 'group_start' tokens does not match the number of 'group_end' tokens.
31
+ #
32
+ def valid?
33
+ raise Pinpoint::Format::UnevenNestingError if group_start_count != group_end_count
34
+
35
+ true
36
+ end
37
+
38
+ private
39
+
40
+ def group_start_count
41
+ count { |token| token.type == :group_start }
42
+ end
43
+
44
+ def group_end_count
45
+ count { |token| token.type == :group_end }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,172 @@
1
+ require 'pinpoint/format/parse_error'
2
+ require 'pinpoint/format/token_list'
3
+ require 'pinpoint/format/token'
4
+
5
+ ###
6
+ # Public: Has the ability to parse a String for specific token identifiers and
7
+ # can process the resulting Tokens using any of the standard Enumerable messages.
8
+ #
9
+ module Pinpoint
10
+ class Format
11
+ class Tokenizer
12
+ include Enumerable
13
+
14
+ ##
15
+ # Public: Initializes a Tokenizer with a given String
16
+ #
17
+ def initialize(string_with_tokens)
18
+ self.tokenable = string_with_tokens
19
+ end
20
+
21
+ ##
22
+ # Public: From the beginning of the tokenable String, it reads each token
23
+ # and passes it to the block.
24
+ #
25
+ # Yields each successive Token to the given block
26
+ #
27
+ # Raises subclasses of Pinpoint::Format::ParseError upon various syntax
28
+ # errors
29
+ #
30
+ # Returns nothing
31
+ #
32
+ def each
33
+ self.tokenable.reset
34
+
35
+ while current_token = next_token
36
+ yield current_token
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Public: Wraps the Array of Tokens in a TokenList
42
+ #
43
+ # Returns a TokenList
44
+ #
45
+ def to_token_list
46
+ TokenList.new(self.to_a)
47
+ end
48
+
49
+ protected
50
+
51
+ # Protected: Reader for tokenable
52
+ attr_reader :tokenable
53
+
54
+ ##
55
+ # Protected: Sets tokenable but first checks to see if it needs to be
56
+ # wrapped in an IO object and does so if necessary.
57
+ #
58
+ def tokenable=(value)
59
+ io = wrap_in_io_if_needed(value)
60
+
61
+ @tokenable = StringScanner.new(io.read)
62
+ end
63
+
64
+ ##
65
+ # Public: Allows the tokens used for the Tokenizer to be overridden.
66
+ #
67
+ # The value passed must be a Hash with keys representing the title of the
68
+ # token and values containing the Regex which represents a match for that
69
+ # Token.
70
+ #
71
+ # Example
72
+ #
73
+ # token_map = { alphanumeric: /[A-Za-z0-9]/
74
+ #
75
+ attr_writer :token_map
76
+
77
+ ##
78
+ # Protected: Reads the token_map for the Tokenizer
79
+ #
80
+ # Returns the token_map if it is set, otherwise sets the token_map to the
81
+ # default and returns it.
82
+ #
83
+ def token_map
84
+ @token_map ||= {
85
+ literal: /[^\(\)%]+/,
86
+ name: /%n/,
87
+ street: /%s/,
88
+ locality: /%l/,
89
+ province: /%p/,
90
+ postal_code: /%z/,
91
+ country: /%c/,
92
+ percent: /%%/,
93
+ left_paren: /%\(/,
94
+ right_paren: /%\)/,
95
+ group_start: /\(/,
96
+ group_end: /\)/
97
+ }
98
+ end
99
+
100
+ private
101
+
102
+ ##
103
+ # Private: Checks to see if the value passed in is an IO or something
104
+ # else. If it's an IO, it remains unmodified, otherwise the value is
105
+ # converted to a StringIO.
106
+ #
107
+ # Example
108
+ #
109
+ # wrap_in_io_if_needed(my_io_object)
110
+ # # => my_io_object
111
+ #
112
+ # wrap_in_io_if_needed('my_string')
113
+ # # => <StringIO contents: 'my_string'>
114
+ #
115
+ # wrap_in_io_if_needed(1)
116
+ # # => <StringIO contents: '1'>
117
+ #
118
+ # Returns an IO object.
119
+ #
120
+ def wrap_in_io_if_needed(value)
121
+ case value
122
+ when IO
123
+ value
124
+ else
125
+ StringIO.new value.to_s
126
+ end
127
+ end
128
+
129
+ ##
130
+ # Private: Can locate the next token in the tokenable String
131
+ #
132
+ # Example
133
+ #
134
+ # # When a Token can be found
135
+ # next_token
136
+ # # => <Token type: :token_type, value: 'my_token'>
137
+ #
138
+ # # When a Token cannot be found
139
+ # next_token
140
+ # # => Pinpoint::Format::ParseError
141
+ #
142
+ # # When there is nothing left to process
143
+ # next_token
144
+ # # => false
145
+ #
146
+ # Returns a Token if a match is found
147
+ # Returns FalseClass if there is nothing left to process
148
+ # Raises Pinpoint::Format::ParseError if an unknown Token is encountered
149
+ #
150
+ def next_token
151
+ return if tokenable.eos?
152
+
153
+ case
154
+ when text = tokenable.scan(token_map[:literal]); Token.new(:literal, text)
155
+ when text = tokenable.scan(token_map[:name]); Token.new(:name, text)
156
+ when text = tokenable.scan(token_map[:street]); Token.new(:street, text)
157
+ when text = tokenable.scan(token_map[:locality]); Token.new(:locality, text)
158
+ when text = tokenable.scan(token_map[:province]); Token.new(:province, text)
159
+ when text = tokenable.scan(token_map[:postal_code]); Token.new(:postal_code, text)
160
+ when text = tokenable.scan(token_map[:country]); Token.new(:country, text)
161
+ when text = tokenable.scan(token_map[:percent]); Token.new(:literal, '%')
162
+ when text = tokenable.scan(token_map[:left_paren]); Token.new(:literal, '(')
163
+ when text = tokenable.scan(token_map[:right_paren]); Token.new(:literal, ')')
164
+ when text = tokenable.scan(token_map[:group_start]); Token.new(:group_start, text)
165
+ when text = tokenable.scan(token_map[:group_end]); Token.new(:group_end, text)
166
+ else
167
+ raise ParseError, "Cannot parse the remainder of the tokenable string: '#{tokenable.rest}'"
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,86 @@
1
+ require 'pinpoint/format/list'
2
+
3
+ module Pinpoint
4
+ class Formatter
5
+
6
+ ##
7
+ # Public: Is able to process an Address into numerous formats based on the
8
+ # country and style, or a completely custom format.
9
+ #
10
+ # address - An Object that responds to #name, #street, #city, #state,
11
+ # #county, #country, #zip_code and #country.
12
+ # options - A Hash of options describing how the address should be formatted
13
+ #
14
+ # :country - Each country displays its addresses in a specific
15
+ # format. Pinpoint knows about these. Passing in two
16
+ # letter ISO code as a Symbol for a country will use
17
+ # that country's format. (defaults to :us)
18
+ # :style - Can be a Symbol which relates to any style in the
19
+ # format file that is loaded. Default formats are:
20
+ #
21
+ # * :one_line
22
+ # * :one_line_with_name
23
+ # * :multi_line
24
+ # * :multi_line_with_name
25
+ # * :html
26
+ #
27
+ # All but the 'html' style is standard text.
28
+ #
29
+ # The :html option wraps each piece of the address as
30
+ # well as the entire address in HTML tags to which CSS
31
+ # can be applied for either a single-line or multi-line
32
+ # layout.
33
+ #
34
+ # Additionally :html will append classes to the
35
+ # different pieces of the address which correspond to
36
+ # both the globally generic name (eg 'locality').
37
+ #
38
+ # Example
39
+ #
40
+ # format(address, :country => :us, :style => :one_line)
41
+ # # => 'Kwik-E-Mart, 123 Apu Lane, Springfield, NW 12345, United States'
42
+ #
43
+ #
44
+ # format(address, :country => :us, :style => :multi_line)
45
+ # # => 'Kwik-E-Mart
46
+ # # 123 Apu Lane
47
+ # # Springfield, NW 12345
48
+ # # United States'
49
+ #
50
+ #
51
+ # format(address, country: :us,
52
+ # style: :html)
53
+ #
54
+ # # => '<address>
55
+ # # <span class="section">
56
+ # # <span class="name">Kwik-E-Mart</span>
57
+ # # </span>
58
+ # # <span class="section">
59
+ # # <span class="street">123 Apu Lane</span>
60
+ # # </span>
61
+ # # <span class="section">
62
+ # # <span class="city locality">Springfield</span>
63
+ # # <span class="state state_or_province">NW</span>
64
+ # # <span class="zip_code postal_code">12345</span>
65
+ # # </span>
66
+ # # <span class="section">
67
+ # # <span class="country">United States</span>
68
+ # # </span>
69
+ # # </address>'
70
+ #
71
+ def self.format(address, options = {})
72
+ country = options.fetch(:country, :us)
73
+ style = options.fetch(:style, :one_line)
74
+
75
+ format = formats[country]
76
+
77
+ format.output address, :style => style
78
+ end
79
+
80
+ private
81
+
82
+ def self.formats
83
+ @formats ||= Pinpoint::Format::List.new
84
+ end
85
+ end
86
+ end
@@ -1,5 +1,5 @@
1
- require 'pinpoint/us_states'
2
- require 'pinpoint/formats'
1
+ require 'pinpoint/config/us_states'
2
+ require 'pinpoint/config/patterns'
3
3
 
4
4
  module Pinpoint
5
5
  module Validations