pikelet 2.0.0.beta.7 → 2.0.0.beta.8
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.
- checksums.yaml +4 -4
- data/README.md +148 -43
- data/lib/pikelet/field_definition.rb +8 -3
- data/lib/pikelet/version.rb +1 -1
- data/spec/readme_examples_spec.rb +177 -0
- data/spec/spec_helper.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c813b4fc0de2c84af10d6e1be5ef863df7dfd89
|
4
|
+
data.tar.gz: a638197dd405056da0024b5f6e9352159e8ba72b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
|
55
|
-
|
|
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="
|
88
|
-
#<struct first_name="
|
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|
|
97
|
-
|ADDR|123 South Street |
|
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
|
-
|
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
|
-
|
118
|
-
|
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
|
-
|
127
|
-
first_name="
|
128
|
-
last_name="
|
129
|
+
type="NAME",
|
130
|
+
first_name="Frida",
|
131
|
+
last_name="Kahlo">,
|
129
132
|
#<struct
|
130
|
-
|
133
|
+
type="ADDR",
|
131
134
|
street_address="123 South Street",
|
132
|
-
city="
|
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
|
153
|
+
but this time some of the records include a middle name:
|
140
154
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
"
|
146
|
-
definition for
|
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
|
-
|
162
|
+
Pikelet.define signature_field: :record_type do
|
163
|
+
record_type 0...5
|
150
164
|
|
151
|
-
record "
|
165
|
+
record "NAME" do
|
152
166
|
first_name 5...15
|
153
167
|
last_name 15...25
|
154
168
|
|
155
|
-
record "
|
156
|
-
|
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
|
-
|
168
|
-
first_name="
|
169
|
-
last_name="
|
181
|
+
record_type="NAME",
|
182
|
+
first_name="Rosa",
|
183
|
+
last_name="Parks">,
|
170
184
|
#<struct
|
171
|
-
|
172
|
-
first_name="
|
173
|
-
last_name="
|
174
|
-
|
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
|
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
|
-
|
37
|
-
|
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
|
|
data/lib/pikelet/version.rb
CHANGED
@@ -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
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.
|
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-
|
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
|