jeti-log 0.5.5 → 0.6.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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +3 -2
- data/Rakefile +7 -2
- data/jeti-log.gemspec +14 -9
- data/lib/jeti/log/data/composite_dataset_builder.rb +3 -11
- data/lib/jeti/log/data/mezon_data.rb +3 -5
- data/lib/jeti/log/data/mgps_data.rb +1 -1
- data/lib/jeti/log/data/mui_data.rb +2 -4
- data/lib/jeti/log/data/rx_data.rb +2 -3
- data/lib/jeti/log/data/tx_data.rb +1 -1
- data/lib/jeti/log/entry.rb +10 -3
- data/lib/jeti/log/file.rb +188 -172
- data/lib/jeti/log/header.rb +1 -1
- data/lib/jeti/log/version.rb +1 -1
- data/spec/file_spec.rb +76 -46
- data/spec/sample-data/goblin-500.log +6419 -0
- data/spec/spec_helper.rb +3 -1
- metadata +108 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de49fe101044ddafde5ef329ef51cafa420d3be5
|
4
|
+
data.tar.gz: b002d696dd02ae2e55ce77e8bee898f4d1e14300
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7336c1217fb00756ac9eaad2dae1a37235e521f990ed8c5234ba901ebb864b158b19f3e50527e4c1c2e9ae1f5c2b27050ea17eb70c04126fbab710c19eae126
|
7
|
+
data.tar.gz: 56b6bd68b68c69ede250932309c8bc6763cf13787652b7ab50b4cdaac548b1ce6e3d2cd17da9f9ebe0202041a2a8b21b547ceea112e333540702043e3b1d8afb
|
data/.rspec
CHANGED
data/.rubocop.yml
ADDED
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
|
-
require
|
2
|
+
require 'bundler/gem_tasks'
|
3
3
|
require 'rspec/core/rake_task'
|
4
4
|
|
5
5
|
RSpec::Core::RakeTask.new('spec')
|
6
|
-
task :
|
6
|
+
task default: :spec
|
7
|
+
|
8
|
+
if ENV['GENERATE_REPORTS'] == 'true'
|
9
|
+
require 'ci/reporter/rake/rspec'
|
10
|
+
task spec: 'ci:setup:rspec'
|
11
|
+
end
|
data/jeti-log.gemspec
CHANGED
@@ -4,28 +4,33 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'jeti/log/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'jeti-log'
|
8
8
|
spec.version = Jeti::Log::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ['Nick Veys']
|
10
|
+
spec.email = ['nick@codelever.com']
|
11
11
|
spec.description = %q{Read and interpret Jeti telemetry log files.}
|
12
12
|
spec.summary = %q{Jeti telemetry log file reader}
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
13
|
+
spec.homepage = 'http://github.com/code-lever/jeti-log'
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_development_dependency 'awesome_print'
|
22
|
-
spec.add_development_dependency 'bundler', '~> 1.
|
23
|
-
spec.add_development_dependency '
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.8'
|
23
|
+
spec.add_development_dependency 'ci_reporter_rspec', '~> 1.0'
|
24
24
|
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
-
spec.add_development_dependency 'rspec', '~>
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.3'
|
26
|
+
spec.add_development_dependency 'rspec-collection_matchers'
|
27
|
+
spec.add_development_dependency 'rspec-its'
|
28
|
+
spec.add_development_dependency 'rubocop'
|
29
|
+
spec.add_development_dependency 'rubocop-checkstyle_formatter'
|
26
30
|
spec.add_development_dependency 'simplecov'
|
27
31
|
spec.add_development_dependency 'simplecov-gem-adapter'
|
28
32
|
spec.add_development_dependency 'simplecov-rcov'
|
33
|
+
spec.add_development_dependency 'yard'
|
29
34
|
|
30
35
|
spec.add_dependency 'ruby_kml', '~> 0.1'
|
31
36
|
end
|
@@ -1,20 +1,12 @@
|
|
1
|
-
module Jeti; module Log; module Data
|
1
|
+
module Jeti; module Log; module Data
|
2
2
|
|
3
3
|
class CompositeDatasetBuilder
|
4
4
|
|
5
5
|
def self.build(file, clazz, device, primary, *others)
|
6
|
-
primaries =
|
7
|
-
file.value_dataset(device, primary[0], primary[1])
|
8
|
-
else
|
9
|
-
file.value_dataset(device, primary)
|
10
|
-
end
|
6
|
+
primaries = file.value_dataset(device, primary)
|
11
7
|
|
12
8
|
other_data = others.map do |other|
|
13
|
-
|
14
|
-
file.value_dataset(device, other[0], other[1])
|
15
|
-
else
|
16
|
-
file.value_dataset(device, other)
|
17
|
-
end
|
9
|
+
file.value_dataset(device, other)
|
18
10
|
end
|
19
11
|
|
20
12
|
primaries.map do |raw|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Jeti; module Log; module Data
|
1
|
+
module Jeti; module Log; module Data
|
2
2
|
|
3
3
|
class MezonData
|
4
4
|
|
@@ -33,10 +33,8 @@ module Jeti; module Log; module Data;
|
|
33
33
|
class MezonDataBuilder
|
34
34
|
|
35
35
|
def self.build(file)
|
36
|
-
|
37
|
-
|
38
|
-
/I Battery/, [/U BEC/, div10], /I BEC/, /Capacity/,
|
39
|
-
/Revolution/, /Temp/, /Run Time/, /PWM/)
|
36
|
+
CompositeDatasetBuilder.build(file, MezonData, /Mezon/i, /U Battery/, /I Battery/,
|
37
|
+
/U BEC/, /I BEC/, /Capacity/, /Revolution/, /Temp/, /Run Time/, /PWM/)
|
40
38
|
end
|
41
39
|
|
42
40
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Jeti; module Log; module Data
|
1
|
+
module Jeti; module Log; module Data
|
2
2
|
|
3
3
|
class MuiData
|
4
4
|
|
@@ -19,9 +19,7 @@ module Jeti; module Log; module Data;
|
|
19
19
|
class MuiDataBuilder
|
20
20
|
|
21
21
|
def self.build(file)
|
22
|
-
|
23
|
-
CompositeDatasetBuilder.build(file, MuiData, /MUI/, [/Voltage/, div10],
|
24
|
-
[/Current/, div10], /Capacity/, /Run time/)
|
22
|
+
CompositeDatasetBuilder.build(file, MuiData, /MUI/, /Voltage/, /Current/, /Capacity/, /Run time/)
|
25
23
|
end
|
26
24
|
|
27
25
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Jeti; module Log; module Data
|
1
|
+
module Jeti; module Log; module Data
|
2
2
|
|
3
3
|
class RxData
|
4
4
|
|
@@ -19,8 +19,7 @@ module Jeti; module Log; module Data;
|
|
19
19
|
class RxDataBuilder
|
20
20
|
|
21
21
|
def self.build(file)
|
22
|
-
|
23
|
-
CompositeDatasetBuilder.build(file, RxData, /Rx/, [/U Rx/, div100], /A1/, /A2/, /Q/)
|
22
|
+
CompositeDatasetBuilder.build(file, RxData, /Rx/, /U Rx/, /A1/, /A2/, /Q/)
|
24
23
|
end
|
25
24
|
|
26
25
|
end
|
data/lib/jeti/log/entry.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module Jeti; module Log
|
1
|
+
module Jeti; module Log
|
2
2
|
|
3
3
|
class Entry
|
4
4
|
|
@@ -24,7 +24,7 @@ module Jeti; module Log;
|
|
24
24
|
raw = detail(sensor_id)
|
25
25
|
case raw[1]
|
26
26
|
when '0','1','4','8'
|
27
|
-
raw[3].to_i
|
27
|
+
format_float(raw[2].to_i, raw[3].to_i)
|
28
28
|
when '5'
|
29
29
|
min = (raw[3].to_i & 0xFF00) >> 8
|
30
30
|
sec = (raw[3].to_i & 0x00FF)
|
@@ -56,6 +56,13 @@ module Jeti; module Log;
|
|
56
56
|
degrees * (((dec >> 1) & 1) == 1 ? -1 : 1)
|
57
57
|
end
|
58
58
|
|
59
|
-
|
59
|
+
def format_float(factor, val)
|
60
|
+
if val > 0xFFFFFF
|
61
|
+
# negative values
|
62
|
+
val = val - 0xFFFFFFFF
|
63
|
+
end
|
60
64
|
|
65
|
+
val * (10 ** -factor)
|
66
|
+
end
|
67
|
+
end
|
61
68
|
end; end
|
data/lib/jeti/log/file.rb
CHANGED
@@ -1,214 +1,230 @@
|
|
1
1
|
require 'open-uri'
|
2
2
|
require 'ruby_kml'
|
3
3
|
|
4
|
-
module Jeti
|
4
|
+
module Jeti
|
5
5
|
|
6
|
-
|
6
|
+
module Log
|
7
7
|
|
8
|
-
|
8
|
+
class File
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
File
|
16
|
-
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# Determines if the file at the given URI is a Jeti telemetry log file.
|
13
|
+
#
|
14
|
+
# @param uri URI to file to read
|
15
|
+
# @return [Jeti::Log::File] loaded file if the file is a Jeti log file, nil otherwise
|
16
|
+
def self.jeti?(uri)
|
17
|
+
File.new(uri) rescue nil
|
18
|
+
end
|
17
19
|
|
18
|
-
|
20
|
+
def initialize(uri)
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
open(uri, 'rb') do |file|
|
23
|
+
lines = file.readlines.map(&:strip).group_by do |line|
|
24
|
+
line.start_with?('#') ? :comments : :rows
|
25
|
+
end
|
26
|
+
@name = /^#(.*)/.match(lines.fetch(:comments, ['# Unknown']).first)[1].strip
|
27
|
+
|
28
|
+
@headers = []
|
29
|
+
@entries = []
|
30
|
+
lines[:rows].each_with_object(';').map(&:split).each do |line|
|
31
|
+
if '000000000' == line.first
|
32
|
+
if line.length == 4
|
33
|
+
line << ''
|
34
|
+
elsif line.length == 5
|
35
|
+
# do nothing
|
36
|
+
else
|
37
|
+
raise RuntimeError, "Unexpected header length (#{line.length})"
|
38
|
+
end
|
39
|
+
@headers << Header.new(line[0], line[1], line[2..4])
|
34
40
|
else
|
35
|
-
|
41
|
+
@entries << Entry.new(line[0], line[1], line[2..-1])
|
36
42
|
end
|
37
|
-
@headers << Header.new(line[0], line[1], line[2..4])
|
38
|
-
else
|
39
|
-
@entries << Entry.new(line[0], line[1], line[2..-1])
|
40
43
|
end
|
44
|
+
|
45
|
+
raise RuntimeError, 'No headers found in log file' if @headers.empty?
|
46
|
+
raise RuntimeError, 'No entries found in log file' if @entries.empty?
|
41
47
|
end
|
42
48
|
|
43
|
-
|
44
|
-
raise
|
49
|
+
rescue => e
|
50
|
+
raise ArgumentError, "File does not appear to be a Jeti log (#{e})"
|
45
51
|
end
|
46
52
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# @return [Float] duration of the session, in seconds
|
54
|
-
def duration
|
55
|
-
(@entries.last.time - @entries.first.time) / 1000.0
|
56
|
-
end
|
53
|
+
# Gets the duration of the session, in seconds.
|
54
|
+
#
|
55
|
+
# @return [Float] duration of the session, in seconds
|
56
|
+
def duration
|
57
|
+
(@entries.last.time - @entries.first.time) / 1000.0
|
58
|
+
end
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
def mgps_data?
|
61
|
+
device_present?(/MGPS/)
|
62
|
+
end
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
-
|
64
|
+
def mgps_data
|
65
|
+
@mgps_data ||= Data::MGPSDataBuilder.build(self)
|
66
|
+
end
|
65
67
|
|
66
|
-
|
67
|
-
|
68
|
-
|
68
|
+
def mezon_data?
|
69
|
+
device_present?(/Mezon/i)
|
70
|
+
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
72
|
+
def mezon_data
|
73
|
+
@mezon_data ||= Data::MezonDataBuilder.build(self)
|
74
|
+
end
|
73
75
|
|
74
|
-
|
75
|
-
|
76
|
-
|
76
|
+
def mui_data?
|
77
|
+
device_present?(/MUI/)
|
78
|
+
end
|
77
79
|
|
78
|
-
|
79
|
-
|
80
|
-
|
80
|
+
def mui_data
|
81
|
+
@mui_data ||= Data::MuiDataBuilder.build(self)
|
82
|
+
end
|
81
83
|
|
82
|
-
|
83
|
-
|
84
|
-
|
84
|
+
def rx_data?
|
85
|
+
device_present?(/Rx/)
|
86
|
+
end
|
85
87
|
|
86
|
-
|
87
|
-
|
88
|
-
|
88
|
+
def rx_data
|
89
|
+
@rx_data ||= Data::RxDataBuilder.build(self)
|
90
|
+
end
|
89
91
|
|
90
|
-
|
91
|
-
|
92
|
-
|
92
|
+
def tx_data?
|
93
|
+
device_present?(/Tx/)
|
94
|
+
end
|
93
95
|
|
94
|
-
|
95
|
-
|
96
|
-
|
96
|
+
def tx_data
|
97
|
+
@tx_data ||= Data::TxDataBuilder.build(self)
|
98
|
+
end
|
97
99
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
100
|
+
# Determines if KML methods can be called for this session.
|
101
|
+
#
|
102
|
+
# @return [Boolean] true if KML can be generated for this session, false otherwise
|
103
|
+
def to_kml?
|
104
|
+
mgps_data?
|
105
|
+
end
|
104
106
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
107
|
+
# Converts the session into a KML document containing a placemark.
|
108
|
+
#
|
109
|
+
# @param file_options [Hash] hash containing options for file
|
110
|
+
# @param placemark_options [Hash] hash containing options for placemark
|
111
|
+
# @return [String] KML document for the session
|
112
|
+
# @see #to_kml_file file options
|
113
|
+
# @see #to_kml_placemark placemark options
|
114
|
+
def to_kml(file_options = {}, placemark_options = {})
|
115
|
+
raise RuntimeError, 'No coordinates available for KML generation' unless to_kml?
|
116
|
+
to_kml_file(file_options, placemark_options).render
|
117
|
+
end
|
116
118
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
:
|
133
|
-
:
|
134
|
-
:
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
119
|
+
# Converts the session into a KMLFile containing a placemark.
|
120
|
+
#
|
121
|
+
# @param file_options [Hash] hash containing options for file
|
122
|
+
# @option file_options [String] :name name option of KML::Document
|
123
|
+
# @option file_options [String] :description name option of KML::Document
|
124
|
+
# @option file_options [String] :style_id id option of KML::Style
|
125
|
+
# @param placemark_options [Hash] hash containing options for placemark
|
126
|
+
# @return [KMLFile] file for the session
|
127
|
+
# @see #to_kml_placemark placemark options
|
128
|
+
def to_kml_file(file_options = {}, placemark_options = {})
|
129
|
+
raise RuntimeError, 'No coordinates available for KML generation' unless to_kml?
|
130
|
+
options = apply_default_file_options(file_options)
|
131
|
+
|
132
|
+
kml = KMLFile.new
|
133
|
+
kml.objects << KML::Document.new(
|
134
|
+
name: options[:name],
|
135
|
+
description: options[:description],
|
136
|
+
styles: [
|
137
|
+
KML::Style.new(
|
138
|
+
id: options[:style_id],
|
139
|
+
line_style: KML::LineStyle.new(color: '7F00FFFF', width: 4),
|
140
|
+
poly_style: KML::PolyStyle.new(color: '7F00FF00')
|
141
|
+
)
|
140
142
|
],
|
141
|
-
:
|
142
|
-
|
143
|
-
|
144
|
-
|
143
|
+
features: [ to_kml_placemark(placemark_options) ]
|
144
|
+
)
|
145
|
+
kml
|
146
|
+
end
|
145
147
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
:
|
162
|
-
:
|
163
|
-
:
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
148
|
+
# Converts the session into a KML::Placemark containing GPS coordinates.
|
149
|
+
#
|
150
|
+
# @param options [Hash] hash containing options for placemark
|
151
|
+
# @option options [String] :altitude_mode altitude_mode option of KML::LineString
|
152
|
+
# @option options [Boolean] :extrude extrude option of KML::LineString
|
153
|
+
# @option options [String] :name name option of KML::Placemark
|
154
|
+
# @option options [String] :style_url style_url option of KML::Placemark
|
155
|
+
# @option options [Boolean] :tessellate tessellate option of KML::LineString
|
156
|
+
# @return [KML::Placemark] placemark for the session
|
157
|
+
def to_kml_placemark(options = {})
|
158
|
+
raise RuntimeError, 'No coordinates available for KML generation' unless to_kml?
|
159
|
+
options = apply_default_placemark_options(options)
|
160
|
+
|
161
|
+
coords = mgps_data.map { |l| [l.longitude, l.latitude, l.altitude] }
|
162
|
+
KML::Placemark.new(
|
163
|
+
name: options[:name],
|
164
|
+
style_url: options[:style_url],
|
165
|
+
geometry: KML::LineString.new(
|
166
|
+
altitude_mode: options[:altitude_mode],
|
167
|
+
extrude: options[:extrude],
|
168
|
+
tessellate: options[:tessellate],
|
169
|
+
coordinates: coords.map { |c| c.join(',') }.join(' ')
|
168
170
|
)
|
169
|
-
|
170
|
-
|
171
|
+
)
|
172
|
+
end
|
171
173
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
174
|
+
def value_dataset(device, sensor)
|
175
|
+
headers, entries = headers_and_entries_for_device(device)
|
176
|
+
sensor_id = (headers.select { |h| sensor =~ h.name })[0].sensor_id
|
177
|
+
entries.reject! { |e| e.detail(sensor_id).nil? }
|
178
|
+
entries.map { |e| [e.time, e.value(sensor_id)] }
|
179
|
+
end
|
178
180
|
|
179
|
-
|
181
|
+
def headers(id = nil)
|
182
|
+
return @headers if id.nil?
|
183
|
+
return @headers.select { |h| h.id == id }
|
184
|
+
end
|
180
185
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
options
|
186
|
-
end
|
186
|
+
def device_present?(device)
|
187
|
+
@headers.any? { |h| device =~ h.name }
|
188
|
+
# XXX improve, make sure there are entries
|
189
|
+
end
|
187
190
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
options = { :name => "Session (#{duration.round(1)}s)" }.merge(options)
|
192
|
-
options = { :style_url => '#default-poly-style' }.merge(options)
|
193
|
-
options = { :tessellate => true }.merge(options)
|
194
|
-
options
|
195
|
-
end
|
191
|
+
def sensor_present?(device, sensor)
|
192
|
+
headers, _entries = headers_and_entries_for_device(device)
|
193
|
+
sensor_headers = (headers.select { |h| sensor =~ h.name })
|
196
194
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
195
|
+
return sensor_headers.count > 0 && sensor_headers.first.sensor_id
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
201
199
|
|
202
|
-
|
203
|
-
|
204
|
-
|
200
|
+
def apply_default_file_options(options)
|
201
|
+
options = { name: 'Jeti MGPS Path' }.merge(options)
|
202
|
+
options = { description: 'Session paths for GPS log data' }.merge(options)
|
203
|
+
options = { style_id: 'default-poly-style' }.merge(options)
|
204
|
+
options
|
205
|
+
end
|
206
|
+
|
207
|
+
def apply_default_placemark_options(options)
|
208
|
+
options = { altitude_mode: 'absolute' }.merge(options)
|
209
|
+
options = { extrude: true }.merge(options)
|
210
|
+
options = { name: "Session (#{duration.round(1)}s)" }.merge(options)
|
211
|
+
options = { style_url: '#default-poly-style' }.merge(options)
|
212
|
+
options = { tessellate: true }.merge(options)
|
213
|
+
options
|
214
|
+
end
|
215
|
+
|
216
|
+
def headers_and_entries_for_device(device)
|
217
|
+
headers = @headers.select { |h| device =~ h.name }
|
218
|
+
return [[],[]] if headers.empty?
|
219
|
+
|
220
|
+
id = headers.first.id
|
221
|
+
headers = @headers.select { |h| h.id == id }
|
222
|
+
entries = @entries.select { |e| e.id == id }
|
223
|
+
[headers, entries]
|
224
|
+
end
|
205
225
|
|
206
|
-
id = headers.first.id
|
207
|
-
headers = @headers.select { |h| h.id == id }
|
208
|
-
entries = @entries.select { |e| e.id == id }
|
209
|
-
[headers, entries]
|
210
226
|
end
|
211
227
|
|
212
228
|
end
|
213
229
|
|
214
|
-
end
|
230
|
+
end
|