pinpoint 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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