jeti-log 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|