pikelet 2.0.0.beta.7 → 2.0.0.beta.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e3adcc8650568a13523518174d2723b61ddf4ba
4
- data.tar.gz: d79cc1a300a4d0356ada007222b6759f98e86c6d
3
+ metadata.gz: 3c813b4fc0de2c84af10d6e1be5ef863df7dfd89
4
+ data.tar.gz: a638197dd405056da0024b5f6e9352159e8ba72b
5
5
  SHA512:
6
- metadata.gz: 6fb05cf2c716cd1cee499cbe4abe9fe4fd6d4bf99b55c0d9bf471a5749dc17d122514f2aebd8d52e671c3dadf5633e69e1183a10ee16275fdbe46897dd0f5546
7
- data.tar.gz: d4b1012c93182ce8d00364ed9d47ccd9ba5003e224c122ccf78d4f33111f08fdf3b3208501a2054fa9aba06b5e75172a7803d57ea6c51a33be97ac7df167d3e3
6
+ metadata.gz: 9c6c7eddcdc7e5b6453e0364e1f2fd3cc8b7a327a3ad8c4c9c09a5c624c3ae4e9d6b81cdae35b55ca3220f19e9fe4d17e294b714e5c04876fd898f9e2fa647b0
7
+ data.tar.gz: 54cce0eef113dd276d2a7971f5b7c5789c53648e0a53e316f145222a99d95627e37f6f98356ba5d2e2da89b13637d00def7d9ce1dedaaef5ae6450cfe7fc59eb
data/README.md CHANGED
@@ -4,14 +4,6 @@
4
4
  [![Build status][build-badge]][build]
5
5
  [![Coverage Status][coverage-badge]][coverage]
6
6
 
7
- ## Beta notes
8
-
9
- The next release of Pikelet will be capable of formatting flat-file databases
10
- for output. As part of this I will be dropping CSV support as it is
11
- constraining my options and, as far as I know, nobody is using it. For the
12
- time being I want to let it evolve as a pure flat-file database parser. In a
13
- future release I may restore CSV support, but I make no promises.
14
-
15
7
  ## Introduction
16
8
 
17
9
  A [pikelet][pikelet-recipe] is a small, delicious pancake popular in Australia
@@ -27,7 +19,7 @@ record types. Each record type has a different structure, though some types
27
19
  share common fields, and all types have a type signature.
28
20
 
29
21
  However, Pikelet will also handle more typical flat-file databases comprised
30
- of homogeneous records.
22
+ of homogeneous records. It can also be used produce data in flat-file format.
31
23
 
32
24
  ## Installation
33
25
 
@@ -51,8 +43,8 @@ Let's say our file is a simple list of first and last names with each field
51
43
  being 10 characters in width, padded with spaces (vertical pipes used to
52
44
  indicate field boundaries).
53
45
 
54
- |Nicolaus |Copernicus|
55
- |Tycho |Brahe |
46
+ |Grace |Hopper |
47
+ |Ada |Lovelace |
56
48
 
57
49
  We can describe this format using Pikelet as follows:
58
50
 
@@ -84,8 +76,19 @@ or this:
84
76
  to an array, or whatever else you people do with enumerators. In any case,
85
77
  what you'll end up with is a series of `Structs` like this:
86
78
 
87
- #<struct first_name="Nicolaus", last_name="Copernicus">,
88
- #<struct first_name="Tycho", last_name="Brahe">
79
+ #<struct first_name="Grace", last_name="Hopper">,
80
+ #<struct first_name="Ada", last_name="Lovelace">
81
+
82
+ You can output these records in flat-file format like so:
83
+
84
+ definition.format(records)
85
+
86
+ Which will return an array of strings:
87
+
88
+ [
89
+ "Grace Hopper ",
90
+ "Ada Lovelace "
91
+ ]
89
92
 
90
93
  ### A more complex case: heterogeneous records
91
94
 
@@ -93,13 +96,13 @@ Now let's say we're given a file consisting of names and addresses, each
93
96
  record contains a 4-character type signature - 'NAME' for names, 'ADDR' for
94
97
  addresses:
95
98
 
96
- |NAME|Nicolaus |Copernicus|
97
- |ADDR|123 South Street |Nowhereville |45678Y |Someplace |
99
+ |NAME|Frida |Kahlo |
100
+ |ADDR|123 South Street |Sometown |45678Y |Someplace |
98
101
 
99
102
  We can describe it as follows:
100
103
 
101
- Pikelet.define do
102
- type_signature 0...4
104
+ definition = Pikelet.define signature_field: :type do
105
+ type 0...4
103
106
 
104
107
  record "NAME" do
105
108
  first_name 4...14
@@ -114,8 +117,8 @@ We can describe it as follows:
114
117
  end
115
118
  end
116
119
 
117
- Note that the type signature is described as a field like any other, but it
118
- must have the name `type_signature`.
120
+ The `signature_field` option tells Pikelet which field to use to determine
121
+ which record type to apply.
119
122
 
120
123
  Each record type is described using `record` statements, which take the
121
124
  record's type signature as a parameter and a block describing its fields.
@@ -123,37 +126,48 @@ record's type signature as a parameter and a block describing its fields.
123
126
  When we parse the data, we end up with this:
124
127
 
125
128
  #<struct
126
- type_signature="NAME",
127
- first_name="Nicolaus",
128
- last_name="Copernicus">,
129
+ type="NAME",
130
+ first_name="Frida",
131
+ last_name="Kahlo">,
129
132
  #<struct
130
- type_signature="ADDR",
133
+ type="ADDR",
131
134
  street_address="123 South Street",
132
- city="Nowhereville",
135
+ city="Sometown",
133
136
  postal_code="45678Y",
134
137
  state="Someplace">
135
138
 
139
+ As with the simple case of homogenous records, calling the `format` method on
140
+ your definition with the records will output an array of strings:
141
+
142
+ [
143
+ "NAMEFrida Kahlo ",
144
+ "ADDR123 South Street Sometown 45678Y Someplace "
145
+ ]
146
+
147
+ Note that each record is padded out to the full width of the widest record
148
+ type.
149
+
136
150
  ### Inheritance
137
151
 
138
152
  Now we go back to our original example, starting with a simple list of names,
139
- but this time some of the records include a nickname:
153
+ but this time some of the records include a middle name:
140
154
 
141
- |PLAIN|Nicolaus |Copernicus|
142
- |FANCY|Tycho |Brahe |Tykester |
155
+ |NAME |Rosa |Parks |
156
+ |NAME+|Rosalind |Franklin |Elsie |
143
157
 
144
158
  The first and last name fields have the same boundaries in each case, but the
145
- "FANCY" records have an additional field. We can describe this by nesting the
146
- definition for FANCY records inside the definition for the PLAIN records:
159
+ "NAME+" records have an additional field. We can describe this by nesting the
160
+ definition for NAME+ records inside the definition for the NAME records:
147
161
 
148
- Pikelet.define do
149
- type_signature 0...5
162
+ Pikelet.define signature_field: :record_type do
163
+ record_type 0...5
150
164
 
151
- record "PLAIN" do
165
+ record "NAME" do
152
166
  first_name 5...15
153
167
  last_name 15...25
154
168
 
155
- record "FANCY" do
156
- nickname 25...35
169
+ record "NAME+" do
170
+ middle_name 25...35
157
171
  end
158
172
  end
159
173
  end
@@ -164,14 +178,14 @@ you might have already figured this out if you were paying attention.
164
178
  Anyway, this is what we get when we parse it.
165
179
 
166
180
  #<struct
167
- type_signature="SIMPLE",
168
- first_name="Nicolaus",
169
- last_name="Copernicus">,
181
+ record_type="NAME",
182
+ first_name="Rosa",
183
+ last_name="Parks">,
170
184
  #<struct
171
- type_signature="FANCY",
172
- first_name="Tycho",
173
- last_name="Brahe",
174
- nickname="Tykester">
185
+ record_type="NAME+",
186
+ first_name="Rosalind",
187
+ last_name="Franklin",
188
+ middle_name="Elsie">
175
189
 
176
190
  ### Custom field parsing
177
191
 
@@ -188,10 +202,101 @@ You can also use shorthand syntax:
188
202
  a_number 0...4, &:to_i
189
203
  end
190
204
 
205
+ A parsers can also be supplied as an option.
206
+
207
+ Pikelet.define do
208
+ a_number 0... 4, parse: ->(value) { value.to_i }
209
+ some_text 4...10, parse: :upcase
210
+ end
211
+
212
+ ### Custom field formatters
213
+
214
+ You can supply a custom formatter for a field.
215
+
216
+ definition = Pikelet.define do
217
+ username 0...10, format: :downcase
218
+ password 10...50, format: ->(v) { Digest::SHA1.hexdigest(v) }
219
+ end
220
+
221
+ definition.format([
222
+ OpenStruct.new(username: "Coleman", password: "password"),
223
+ OpenStruct.new(username: "Savitskaya", password: "sekrit" )
224
+ ])
225
+
226
+ This will produce the following array of strings:
227
+
228
+ [
229
+ "coleman 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
230
+ "savitskaya8d42e738c7adee551324955458b5e2c0b49ee655"
231
+ ]
232
+
233
+ ### Formatting options
234
+
235
+ In addition to custom formatters, you can provide alignment and padding
236
+ options.
237
+
238
+ definition = Pikelet.define do
239
+ number 0... 3, align: :right, pad: "0"
240
+ text 3...10, align: :left, pad: " "
241
+ end
242
+
243
+ There is also a `type` option, which is a shorthand for default alpha and
244
+ numeric formatting.
245
+
246
+ definition = Pikelet.define do
247
+ number 0... 3, type: :numeric # right-align, pad with zeroes
248
+ text 3...10, type: :alpha # left-align, pad with spaces
249
+ end
250
+
251
+ ### Custom record classes
252
+
253
+ By default Pikelet will return records as `Struct` objects, but you can supply
254
+ a custom class to use instead.
255
+
256
+ class Base
257
+ attr_reader :type
258
+
259
+ def initialize(**attrs)
260
+ @type = attrs[:type]
261
+ end
262
+ end
263
+
264
+ class Name < Base
265
+ attr_reader :name
266
+
267
+ def initialize(**attrs)
268
+ super(type: "NAME")
269
+ @name = attrs[:name]
270
+ end
271
+ end
272
+
273
+ class Address < Base
274
+ attr_reader :street, :city
275
+
276
+ def initialize(**attrs)
277
+ super(type: "ADDR")
278
+ @street = attrs[:street]
279
+ @city = attrs[:city]
280
+ end
281
+ end
282
+
283
+ Pikelet.define signature_field: :type, record_class: Base do
284
+ type 0...4
285
+
286
+ record "NAME", record_class: Name do
287
+ name 4...20
288
+ end
289
+
290
+ record "ADDR", record_class: Address do
291
+ street 4...20
292
+ city 20...30
293
+ end
294
+ end
295
+
296
+ The only requirement on the class is that its constructor (ie. `initialize`
297
+ method) should accept attributes as a hash with symbol keys.
191
298
  ## Thoughts/plans
192
299
 
193
- * With some work, Pikelet could produce flat file records as easily as it
194
- consumes them.
195
300
  * I had a crack at supporting lazy enumeration, and it kinda works. Sometimes.
196
301
  If the moon is in the right quarter. I'd like to get it working properly.
197
302
 
@@ -11,7 +11,7 @@ module Pikelet
11
11
 
12
12
  @index = index
13
13
  @width = index.size
14
- @parser = parse || block || :strip
14
+ @parser = parse || block
15
15
  @formatter = format || :to_s
16
16
 
17
17
  if type == :numeric
@@ -33,8 +33,13 @@ module Pikelet
33
33
  end
34
34
 
35
35
  def parse(record)
36
- if value = record[index]
37
- parser.to_proc.call(value)
36
+ # TODO: Test that fields are always stripped.
37
+ if value = record[index].strip
38
+ if parser
39
+ parser.to_proc.call(value)
40
+ else
41
+ value
42
+ end
38
43
  end
39
44
  end
40
45
 
@@ -1,3 +1,3 @@
1
1
  module Pikelet
2
- VERSION = "2.0.0.beta.7"
2
+ VERSION = "2.0.0.beta.8"
3
3
  end
@@ -0,0 +1,177 @@
1
+ require "spec_helper"
2
+ require "pikelet"
3
+
4
+ describe "README Examples:" do
5
+ def strip_data(text)
6
+ text.gsub(/^#{text.scan(/^[ \t]*(?=\S)/).min || ''}/, '').gsub('|', '').split("\n")
7
+ end
8
+
9
+ RSpec::Matchers.define :match do |expected|
10
+ def record_matches_hash?(record, hash)
11
+ hash.all? { |attr, value| record.send(attr) == value }
12
+ end
13
+
14
+ match do |actual|
15
+ actual.zip(expected).all? do |actual, expected|
16
+ record_matches_hash?(actual, expected)
17
+ end
18
+ end
19
+ end
20
+
21
+ shared_examples_for "parse the data" do
22
+ subject { definition.parse(strip_data(data)) }
23
+
24
+ it { is_expected.to match expected_records }
25
+ end
26
+
27
+ shared_examples_for "format the records" do
28
+ subject { definition.format(records) }
29
+
30
+ it { is_expected.to eq strip_data(expected_data) }
31
+ end
32
+
33
+ describe "Homogeneous records" do
34
+ let(:definition) do
35
+ Pikelet.define do
36
+ first_name 0...10
37
+ last_name 10...20
38
+ end
39
+ end
40
+
41
+ let(:data) do
42
+ <<-DATA
43
+ |Grace |Hopper |
44
+ |Ada |Lovelace |
45
+ DATA
46
+ end
47
+
48
+ let(:expected_records) do
49
+ [
50
+ { first_name: "Grace", last_name: "Hopper" },
51
+ { first_name: "Ada", last_name: "Lovelace" }
52
+ ]
53
+ end
54
+
55
+ it_will "parse the data"
56
+ end
57
+
58
+ describe "Heterogeneous records" do
59
+ let(:definition) do
60
+ Pikelet.define signature_field: :type do
61
+ type 0...4
62
+
63
+ record "NAME" do
64
+ first_name 4...14
65
+ last_name 14...24
66
+ end
67
+
68
+ record "ADDR" do
69
+ street_address 4...24
70
+ city 24...44
71
+ postal_code 44...54
72
+ state 54...74
73
+ end
74
+ end
75
+ end
76
+
77
+ let(:data) do
78
+ <<-DATA
79
+ |NAME|Frida |Kahlo |
80
+ |ADDR|123 South Street |Sometown |45678Y |Someplace |
81
+ DATA
82
+ end
83
+
84
+ let(:expected_records) do
85
+ [
86
+ { type: "NAME", first_name: "Frida", last_name: "Kahlo" },
87
+ { type: "ADDR", street_address: "123 South Street", city: "Sometown", postal_code: "45678Y", state: "Someplace" }
88
+ ]
89
+ end
90
+
91
+ it_will "parse the data"
92
+ end
93
+
94
+ describe "Inheritance" do
95
+ let(:definition) do
96
+ Pikelet.define signature_field: :record_type do
97
+ record_type 0...5
98
+
99
+ record "NAME" do
100
+ first_name 5...15
101
+ last_name 15...25
102
+
103
+ record "NAME+" do
104
+ middle_name 25...35
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ let(:data) do
111
+ <<-DATA
112
+ |NAME |Rosa |Parks |
113
+ |NAME+|Rosalind |Franklin |Elsie |
114
+ DATA
115
+ end
116
+
117
+ let(:expected_records) do
118
+ [
119
+ { record_type: "NAME", first_name: "Rosa", last_name: "Parks" },
120
+ { record_type: "NAME+", first_name: "Rosalind", last_name: "Franklin", middle_name: "Elsie" }
121
+ ]
122
+ end
123
+
124
+ it_will "parse the data"
125
+ end
126
+
127
+ describe "Custom field parsing" do
128
+ let(:definition) do
129
+ Pikelet.define do
130
+ a_number(0... 4) { |value| value.to_i }
131
+
132
+ another_number 4... 8, &:to_i
133
+ yet_another_number 8...12, parse: ->(value) { value.to_i }
134
+ some_text 12...20, parse: :upcase
135
+ end
136
+ end
137
+
138
+ let(:data) do
139
+ <<-DATA
140
+ | 67| 3| 999|blah |
141
+ DATA
142
+ end
143
+
144
+ let(:expected_records) do
145
+ [
146
+ { a_number: 67, another_number: 3, yet_another_number: 999, some_text: "BLAH" }
147
+ ]
148
+ end
149
+
150
+ it_will "parse the data"
151
+ end
152
+
153
+ describe "Custom field formatting" do
154
+ let(:definition) do
155
+ Pikelet.define do
156
+ username 0...10, format: :downcase
157
+ password 10...50, format: ->(v) { Digest::SHA1.hexdigest(v) }
158
+ end
159
+ end
160
+
161
+ let(:records) do
162
+ [
163
+ OpenStruct.new(username: "Coleman", password: "password"),
164
+ OpenStruct.new(username: "Savitskaya", password: "sekrit" )
165
+ ]
166
+ end
167
+
168
+ let(:expected_data) do
169
+ <<-DATA
170
+ |coleman |5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8|
171
+ |savitskaya|8d42e738c7adee551324955458b5e2c0b49ee655|
172
+ DATA
173
+ end
174
+
175
+ it_will "format the records"
176
+ end
177
+ end
data/spec/spec_helper.rb CHANGED
@@ -83,4 +83,6 @@ RSpec.configure do |config|
83
83
  # a real object. This is generally recommended.
84
84
  mocks.verify_partial_doubles = true
85
85
  end
86
+
87
+ config.alias_it_behaves_like_to :it_will, "it will"
86
88
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pikelet
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta.7
4
+ version: 2.0.0.beta.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Carney
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-24 00:00:00.000000000 Z
11
+ date: 2015-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -136,6 +136,7 @@ files:
136
136
  - spec/pikelet/record_definer_spec.rb
137
137
  - spec/pikelet/record_definition_spec.rb
138
138
  - spec/pikelet_spec.rb
139
+ - spec/readme_examples_spec.rb
139
140
  - spec/spec_helper.rb
140
141
  homepage: ''
141
142
  licenses:
@@ -166,4 +167,5 @@ test_files:
166
167
  - spec/pikelet/record_definer_spec.rb
167
168
  - spec/pikelet/record_definition_spec.rb
168
169
  - spec/pikelet_spec.rb
170
+ - spec/readme_examples_spec.rb
169
171
  - spec/spec_helper.rb