rubyfit 0.0.6 → 0.0.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: 33df17859113bfabf926430108e48e832a16609d
4
- data.tar.gz: 67bf9d1069e605c1bd4fe8402c442a3eb9d55531
3
+ metadata.gz: 250aefed2638033617a97b1789e967c90a26940a
4
+ data.tar.gz: 46b607c278b112a5dffa6519d54f0bb2c585b0fa
5
5
  SHA512:
6
- metadata.gz: c79dd06cb89f79330f458b2a7aae56cbd9dd7dbd30a1bfc7054931c960623b66097226a8c6a729e75dda582c4928d652438eadc4c82f631fbde07636652739f7
7
- data.tar.gz: 5ba13d8485c785f17f2e25a90e940b04eb54272e099eea0f1167615acf432813e5b7023eb702c04679b62b2d098ee6cbdf03271151ff00ec309a6467570e0469
6
+ metadata.gz: d7fbc3064d7e5f8af5947228731c0c2f3213ea196cf4d9f0b2045968216bd41864e04d17c8a646694c5e3ac3541b73fe95861b2574ffa8d84453b9765263e9ca
7
+ data.tar.gz: 848eaca18d3bb03a830405671fd1c43e51020bfd51f4a6a66378c225b31e8b29f9d13bf82919c9473bf6fc77bbe03f76cecd443935b19d3ab337ab89b75ec307
@@ -18,6 +18,7 @@
18
18
  #include "math.h"
19
19
 
20
20
  #include "fit_convert.h"
21
+ #include "fit_crc.h"
21
22
 
22
23
  VALUE mRubyFit;
23
24
  VALUE cFitParser;
@@ -540,6 +541,13 @@ static VALUE parse(VALUE self, VALUE original_str) {
540
541
  return Qnil;
541
542
  }
542
543
 
544
+ static VALUE update_crc(VALUE self, VALUE r_crc, VALUE r_data) {
545
+ FIT_UINT16 crc = NUM2USHORT(r_crc);
546
+ const char* data = StringValuePtr(r_data);
547
+ const FIT_UINT16 byte_count = RSTRING_LEN(r_data);
548
+ return UINT2NUM(FitCRC_Update16(crc, data, byte_count));
549
+ }
550
+
543
551
  void Init_rubyfit() {
544
552
  mRubyFit = rb_define_module("RubyFit");
545
553
  cFitParser = rb_define_class_under(mRubyFit, "FitParser", rb_cObject);
@@ -551,4 +559,8 @@ void Init_rubyfit() {
551
559
  //attributes
552
560
  HANDLER_ATTR = rb_intern("@handler");
553
561
  rb_define_attr(cFitParser, "handler", 1, 1);
562
+
563
+ // CRC helper
564
+ VALUE mCRC = rb_define_module_under(mRubyFit, "CRC");
565
+ rb_define_singleton_method(mCRC, "update_crc", update_crc, 2);
554
566
  }
@@ -1,2 +1,5 @@
1
1
  require "rubyfit/version"
2
2
  require 'rubyfit/rubyfit'
3
+
4
+ require 'rubyfit/writer'
5
+ require 'rubyfit/helpers'
@@ -0,0 +1,107 @@
1
+ module RubyFit::Helpers
2
+ # Garmin timestamps start at 12:00:00 01-01-1989, 20 years after the unix epoch
3
+ GARMIN_TIME_OFFSET = 631065600
4
+
5
+ DEGREES_TO_SEMICIRCLES = 2**31 / 180.0
6
+
7
+ # Converts a fixnum or bignum into a byte array, optionally
8
+ # truncating or right-filling with 0 to match a certain size
9
+ def num2bytes(num, byte_count, big_endian = true)
10
+ raise ArgumentError.new("num must be an integer") unless num.is_a?(Integer)
11
+ orig_num = num
12
+ # Convert negative numbers to two's complement (1-byte alignment)
13
+ if num < 0
14
+ num = num.abs
15
+
16
+ if num > 2 ** (byte_count * 8 - 1)
17
+ STDERR.puts("RubyFit WARNING: Integer underflow for #{orig_num} (#{orig_num.bit_length + 1} bits) when fitting in #{byte_count} bytes (#{byte_count * 8} bits)")
18
+ end
19
+
20
+ num = 2 ** (byte_count * 8) - num
21
+ end
22
+
23
+ hex = num.to_s(16)
24
+ # pack('H*') assumes the high nybble is first, which reverses nybbles in
25
+ # the most significant byte if it's only one hex char (<= 0xF). Prevent
26
+ # this by prepending a zero if the hex string is an odd length
27
+ hex = "0" + hex if hex.length.odd?
28
+ result = [hex]
29
+ .pack('H*')
30
+ .unpack("C*")
31
+
32
+ if result.size > byte_count
33
+ STDERR.puts("RubyFit WARNING: Truncating #{orig_num} (#{orig_num.bit_length} bits) to fit in #{byte_count} bytes (#{byte_count * 8} bits)")
34
+ result = result.last(byte_count)
35
+ elsif result.size < byte_count
36
+ pad_bytes = [0] * (byte_count - result.size)
37
+ result.unshift(*pad_bytes)
38
+ end
39
+
40
+ result.reverse! unless big_endian
41
+
42
+ result
43
+ end
44
+
45
+ def bytes2num(bytes, byte_count, unsigned = true, big_endian = true)
46
+ directive = {
47
+ 1 => "C",
48
+ 2 => "S",
49
+ 4 => "L",
50
+ 8 => "Q"
51
+ }[byte_count]
52
+ raise "Unsupported byte count: #{byte_count}" unless directive
53
+ directive << (big_endian ? ">" : "<") if byte_count > 1
54
+ directive.downcase! unless unsigned
55
+ bytes.pack("C*").unpack(directive).first
56
+ end
57
+
58
+ # Converts an ASCII string into a byte array, truncating or right-filling
59
+ # with 0 to match byte_count
60
+ def str2bytes(str, byte_count)
61
+ str
62
+ .unpack("C#{byte_count - 1}") # Convert to n-1 bytes
63
+ .map{|v| v || 0} + [0] # Convert nils to 0 and add null terminator
64
+ end
65
+
66
+ # Converts a byte array to a string. Omits the last character of the byte
67
+ # array from the result if it is 0
68
+ def bytes2str(bytes)
69
+ bytes = bytes[0...-1] if bytes.last == 0
70
+ bytes.pack("C*")
71
+ end
72
+
73
+ # Generates strings of hex bytes (for debugging)
74
+ def bytes2hex(bytes)
75
+ bytes
76
+ .map{|b| "0x#{b.to_s(16).ljust(2, "0")}"}
77
+ .each_slice(8)
78
+ .map{ |s| s.join(", ") }
79
+ end
80
+
81
+ def unix2fit_timestamp(timestamp)
82
+ timestamp - GARMIN_TIME_OFFSET
83
+ end
84
+
85
+ def fit2unix_timestamp(timestamp)
86
+ timestamp + GARMIN_TIME_OFFSET
87
+ end
88
+
89
+
90
+ def deg2semicircles(degrees)
91
+ (degrees * DEGREES_TO_SEMICIRCLES).truncate
92
+ end
93
+
94
+ def semicircles2deg(degrees)
95
+ result = degrees / DEGREES_TO_SEMICIRCLES
96
+ result -= 360.0 if result > 180.0
97
+ result += 360.0 if result < -180.0
98
+ result
99
+ end
100
+
101
+ def make_message_header(opts = {})
102
+ result = 0
103
+ result |= (1 << 6) if opts[:definition]
104
+ result |= (opts[:local_number] || 0) & 0xF
105
+ result
106
+ end
107
+ end
@@ -0,0 +1,176 @@
1
+ require "rubyfit/type"
2
+ require "rubyfit/helpers"
3
+
4
+ class RubyFit::MessageWriter
5
+ extend RubyFit::Helpers
6
+
7
+ FIT_PROTOCOL_VERSION = 0x10 # major 1, minor 0
8
+ FIT_PROFILE_VERSION = 1 * 100 + 52 # major 1, minor 52
9
+
10
+ COURSE_POINT_TYPE = {
11
+ invalid: 255,
12
+ generic: 0,
13
+ summit: 1,
14
+ valley: 2,
15
+ water: 3,
16
+ food: 4,
17
+ danger: 5,
18
+ left: 6,
19
+ right: 7,
20
+ straight: 8,
21
+ first_aid: 9,
22
+ fourth_category: 10,
23
+ third_category: 11,
24
+ second_category: 12,
25
+ first_category: 13,
26
+ hors_category: 14,
27
+ sprint: 15,
28
+ left_fork: 16,
29
+ right_fork: 17,
30
+ middle_fork: 18,
31
+ slight_left: 19,
32
+ sharp_left: 20,
33
+ slight_right: 21,
34
+ sharp_right: 22,
35
+ u_turn: 23,
36
+ segment_start: 24,
37
+ segment_end: 25
38
+ }.freeze
39
+
40
+ MESSAGE_DEFINITIONS = {
41
+ file_id: {
42
+ id: 0,
43
+ fields: {
44
+ serial_number: { id: 3, type: RubyFit::Type.uint32z, required: true },
45
+ time_created: { id: 4, type: RubyFit::Type.timestamp, required: true },
46
+ manufacturer: { id: 1, type: RubyFit::Type.uint16 }, # See FIT_MANUFACTURER_*
47
+ product: { id: 2, type: RubyFit::Type.uint16 },
48
+ type: { id: 0, type: RubyFit::Type.enum, required: true }, # See FIT_FILE_*
49
+ }
50
+ },
51
+ course: {
52
+ id: 31,
53
+ fields: {
54
+ name: { id: 5, type: RubyFit::Type.string(16), required: true },
55
+ }
56
+ },
57
+ lap: {
58
+ id: 19,
59
+ fields: {
60
+ timestamp: { id: 253, type: RubyFit::Type.timestamp, required: true},
61
+ start_time: { id: 2, type: RubyFit::Type.timestamp, required: true},
62
+ start_y: { id: 3, type: RubyFit::Type.semicircles },
63
+ start_x: { id: 4, type: RubyFit::Type.semicircles },
64
+ end_y: { id: 5, type: RubyFit::Type.semicircles },
65
+ end_x: { id: 6, type: RubyFit::Type.semicircles },
66
+ total_distance: { id: 9, type: RubyFit::Type.centimeters },
67
+ },
68
+ },
69
+ course_point: {
70
+ id: 32,
71
+ fields: {
72
+ timestamp: { id: 1, type: RubyFit::Type.timestamp, required: true },
73
+ y: { id: 2, type: RubyFit::Type.semicircles, required: true },
74
+ x: { id: 3, type: RubyFit::Type.semicircles, required: true },
75
+ distance: { id: 4, type: RubyFit::Type.centimeters },
76
+ name: { id: 6, type: RubyFit::Type.string(16) },
77
+ message_index: { id: 254, type: RubyFit::Type.uint16 },
78
+ type: { id: 5, type: RubyFit::Type.enum, values: COURSE_POINT_TYPE, required: true }
79
+ },
80
+ },
81
+ record: {
82
+ id: 20,
83
+ fields: {
84
+ timestamp: { id: 253, type: RubyFit::Type.timestamp, required: true },
85
+ y: { id: 0, type: RubyFit::Type.semicircles, required: true },
86
+ x: { id: 1, type: RubyFit::Type.semicircles, required: true },
87
+ distance: { id: 5, type: RubyFit::Type.centimeters },
88
+ elevation: { id: 2, type: RubyFit::Type.altitude },
89
+ }
90
+ }
91
+ }
92
+
93
+ def self.definition_message(type, local_num)
94
+ pack_bytes do |bytes|
95
+ message_data = MESSAGE_DEFINITIONS[type]
96
+ bytes << header_byte(local_num, true)
97
+ bytes << 0x00 # Reserved uint8
98
+ bytes << 0x01 # Big endian
99
+ bytes.push(*num2bytes(message_data[:id], 2)) # Global message ID
100
+ bytes << message_data[:fields].size # Field count
101
+
102
+ message_data[:fields].each do |field, info|
103
+ type = info[:type]
104
+ bytes << info[:id]
105
+ bytes << type.byte_count
106
+ bytes << type.fit_id
107
+ end
108
+ end
109
+ end
110
+
111
+ def self.data_message(type, local_num, values)
112
+ pack_bytes do |bytes|
113
+ message_data = MESSAGE_DEFINITIONS[type]
114
+ bytes << header_byte(local_num, false)
115
+ message_data[:fields].each do |field, info|
116
+ field_type = info[:type]
117
+ value = values[field]
118
+ if info[:required] && value.nil?
119
+ raise ArgumentError.new("Missing required field '#{field}' in #{type} data message values")
120
+ end
121
+
122
+ if info[:values]
123
+ value = info[:values][value]
124
+ if value.nil?
125
+ raise ArgumentError.new("Invalid value for '#{field}' in #{type} data message values")
126
+ end
127
+ end
128
+
129
+ value_bytes = value ? field_type.val2bytes(value) : field_type.default_bytes
130
+ bytes.push(*value_bytes)
131
+ end
132
+ end
133
+ end
134
+
135
+ def self.definition_message_size(type)
136
+ message_data = MESSAGE_DEFINITIONS[type]
137
+ raise ArgumentError.new("Unknown message type '#{type}'") unless message_data
138
+ 6 + message_data[:fields].count * 3
139
+ end
140
+
141
+ def self.data_message_size(type)
142
+ message_data = MESSAGE_DEFINITIONS[type]
143
+ raise ArgumentError.new("Unknown message type '#{type}'") unless message_data
144
+ 1 + message_data[:fields].values.map{|info| info[:type].byte_count}.reduce(&:+)
145
+ end
146
+
147
+ def self.file_header(data_byte_count = 0)
148
+ pack_bytes do |bytes|
149
+ bytes << 14 # Header size
150
+ bytes << FIT_PROTOCOL_VERSION # Protocol version
151
+ bytes.push(*num2bytes(FIT_PROFILE_VERSION, 2).reverse) # Profile version (little endian)
152
+ bytes.push(*num2bytes(data_byte_count, 4).reverse) # Data size (little endian)
153
+ bytes.push(*str2bytes(".FIT", 5).take(4)) # Data Type ASCII, no terminator
154
+ crc = 0 #RubyFit::CRC.update_crc(0, bytes2str(bytes))
155
+ bytes.push(*num2bytes(crc, 2).reverse) # Header CRC (little endian)
156
+ end
157
+ end
158
+
159
+ def self.crc(crc_value)
160
+ pack_bytes do |bytes|
161
+ bytes.push(*num2bytes(crc_value, 2, false)) # Little endian
162
+ end
163
+ end
164
+
165
+ # Internal
166
+
167
+ def self.header_byte(local_number, definition)
168
+ local_number & 0xF | (definition ? 0x40 : 0x00)
169
+ end
170
+
171
+ def self.pack_bytes
172
+ bytes = []
173
+ yield bytes
174
+ bytes.pack("C*")
175
+ end
176
+ end
@@ -0,0 +1,155 @@
1
+ require "rubyfit/helpers"
2
+
3
+ class RubyFit::Type
4
+ attr_reader *%i(fit_id byte_count default_bytes)
5
+
6
+ def initialize(opts = {})
7
+ @val2bytes = opts[:val2bytes]
8
+ @bytes2val = opts[:bytes2val]
9
+ @rb2fit = opts[:rb2fit]
10
+ @fit2rb = opts[:fit2rb]
11
+ @default_bytes = opts[:default_bytes]
12
+ @byte_count = opts[:byte_count]
13
+ @fit_id = opts[:fit_id]
14
+ end
15
+
16
+ def val2bytes(val)
17
+ result = val
18
+ result = @rb2fit.call(result, self) if @rb2fit
19
+ result = @val2bytes.call(result, self)
20
+ result
21
+ end
22
+
23
+ def bytes2val(bytes)
24
+ result = bytes
25
+ result = @bytes2val.call(result, self)
26
+ result = @fit2rb.call(result, self) if @fit2rb
27
+ result
28
+ end
29
+
30
+ class << self
31
+ include RubyFit::Helpers
32
+
33
+ def integer(opts = {})
34
+ unsigned = opts.delete(:unsigned)
35
+ default = opts[:default]
36
+
37
+ # Default (invalid) value for integers is the maximum positive value
38
+ # given the bit length and whether the data is signed/unsigned
39
+ unless default
40
+ bit_count = opts[:byte_count] * 8
41
+ bit_count -= 1 unless unsigned
42
+ default = 2**bit_count - 1
43
+ end
44
+
45
+ new({
46
+ default_bytes: num2bytes(default, opts[:byte_count]),
47
+ val2bytes: ->(val, type) { num2bytes(val, type.byte_count) },
48
+ bytes2val: ->(bytes, type) { bytes2num(bytes, type.byte_count, unsigned) },
49
+ }.merge(opts))
50
+ end
51
+
52
+ # Base Types #
53
+
54
+ def enum(opts = {})
55
+ uint8(fit_id: 0x00)
56
+ end
57
+
58
+ def string(byte_count, opts = {})
59
+ new({
60
+ fit_id: 0x07,
61
+ byte_count: byte_count,
62
+ default_bytes: [0x00] * byte_count,
63
+ val2bytes: ->(val, type) { str2bytes(val, type.byte_count) },
64
+ bytes2val: ->(bytes, type) { bytes2str(bytes) },
65
+ }.merge(opts))
66
+ end
67
+
68
+ def byte(byte_count, opts = {})
69
+ new({
70
+ fit_id: 0x0D,
71
+ default_bytes: [0xFF] * length,
72
+ val2bytes: ->(val) { val },
73
+ bytes2val: ->(bytes) { bytes },
74
+ }.merge(opts))
75
+ end
76
+
77
+ def sint8(opts = {})
78
+ integer({unsigned: false, byte_count: 1, fit_id: 0x01}.merge(opts))
79
+ end
80
+
81
+ def uint8(opts = {})
82
+ integer({unsigned: true, byte_count: 1, fit_id: 0x02}.merge(opts))
83
+ end
84
+
85
+ def sint16(opts = {})
86
+ integer({unsigned: false, byte_count: 2, fit_id: 0x83}.merge(opts))
87
+ end
88
+
89
+ def uint16(opts = {})
90
+ integer({unsigned: true, byte_count: 2, fit_id: 0x84}.merge(opts))
91
+ end
92
+
93
+ def sint32(opts = {})
94
+ integer({unsigned: false, byte_count: 4, fit_id: 0x85}.merge(opts))
95
+ end
96
+
97
+ def uint32(opts = {})
98
+ integer({unsigned: true, byte_count: 4, fit_id: 0x86}.merge(opts))
99
+ end
100
+
101
+ def sint64(opts = {})
102
+ integer({unsigned: false, byte_count: 8, fit_id: 0x8E}.merge(opts))
103
+ end
104
+
105
+ def uint64(opts = {})
106
+ integer({unsigned: true, byte_count: 8, fit_id: 0x8F}.merge(opts))
107
+ end
108
+
109
+ def uint8z(opts = {})
110
+ integer({unsigned: true, default: 0, byte_count: 1, fit_id: 0x0A}.merge(opts))
111
+ end
112
+
113
+ def uint16z(opts = {})
114
+ integer({unsigned: true, default: 0, byte_count: 2, fit_id: 0x8B}.merge(opts))
115
+ end
116
+
117
+ def uint32z(opts = {})
118
+ integer({unsigned: true, default: 0, byte_count: 4, fit_id: 0x8C}.merge(opts))
119
+ end
120
+
121
+ def uint64z(opts = {})
122
+ integer({unsigned: true, default: 0, byte_count: 8, fit_id: 0x90}.merge(opts))
123
+ end
124
+
125
+ # Derived types
126
+
127
+ def timestamp
128
+ uint32({
129
+ rb2fit: ->(val, type) { unix2fit_timestamp(val) },
130
+ fit2rb: ->(val, type) { fit2unix_timestamp(val) }
131
+ })
132
+ end
133
+
134
+ def semicircles
135
+ sint32({
136
+ rb2fit: ->(val, type) { deg2semicircles(val) },
137
+ fit2rb: ->(val, type) { semicircles2deg(val) }
138
+ })
139
+ end
140
+
141
+ def centimeters
142
+ uint32({
143
+ rb2fit: ->(val, type) { (val * 100).truncate },
144
+ fit2rb: ->(val, type) { val / 100.0 }
145
+ })
146
+ end
147
+
148
+ def altitude
149
+ uint16({
150
+ rb2fit: ->(val, type) { (val + 500).truncate },
151
+ fit2rb: ->(val, type) { val - 500 }
152
+ })
153
+ end
154
+ end
155
+ end
@@ -1,3 +1,3 @@
1
1
  module Rubyfit
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -0,0 +1,118 @@
1
+ require "rubyfit/message_writer"
2
+
3
+ class RubyFit::Writer
4
+ def write(stream, opts = {})
5
+ raise "Can't start write mode from #{@state}" if @state
6
+ @state = :write
7
+ @local_nums = []
8
+
9
+ @stream = stream
10
+
11
+ %i(start_time end_time course_point_count track_point_count name
12
+ total_distance time_created start_x start_y end_x end_y).each do |key|
13
+ raise ArgumentError.new("Missing required option #{key}") unless opts[key]
14
+ end
15
+
16
+ start_time = opts[:start_time].to_i
17
+ end_time = opts[:end_time].to_i
18
+
19
+ @data_crc = 0
20
+
21
+ # Calculate data size to put in header
22
+ definition_sizes = %i(file_id course lap course_point record)
23
+ .map{|type| RubyFit::MessageWriter.definition_message_size(type) }
24
+ .reduce(&:+)
25
+
26
+ data_sizes = {
27
+ file_id: 1,
28
+ course: 1,
29
+ lap: 1,
30
+ course_point: opts[:course_point_count],
31
+ record: opts[:track_point_count]
32
+ }
33
+ .map{|type, count| RubyFit::MessageWriter.data_message_size(type) * count }
34
+ .reduce(&:+)
35
+
36
+ data_size = definition_sizes + data_sizes
37
+ write_data(RubyFit::MessageWriter.file_header(data_size))
38
+
39
+ write_definition_message(:file_id)
40
+ write_data_message(:file_id, {
41
+ time_created: opts[:time_created],
42
+ type: 6, # Course file
43
+ manufacturer: 1, # Garmin
44
+ product: 0,
45
+ serial_number: 0,
46
+ })
47
+
48
+ write_definition_message(:course)
49
+ write_data_message(:course, { name: opts[:name] })
50
+
51
+ write_definition_message(:lap)
52
+ write_data_message(:lap, {
53
+ start_time: start_time,
54
+ timestamp: end_time,
55
+ start_x: opts[:start_x],
56
+ start_y: opts[:start_y],
57
+ end_x: opts[:end_x],
58
+ end_y: opts[:end_y],
59
+ total_distance: opts[:total_distance]
60
+ })
61
+
62
+ yield
63
+
64
+ write_data(RubyFit::MessageWriter.crc(@data_crc))
65
+ @state = nil
66
+ end
67
+
68
+ def course_points
69
+ raise "Can only start course points mode inside 'write' block" if @state != :write
70
+ @state = :course_points
71
+ write_definition_message(:course_point)
72
+ yield
73
+ @state = :write
74
+ end
75
+
76
+ def track_points
77
+ raise "Can only write track points inside 'write' block" if @state != :write
78
+ @state = :track_points
79
+ write_definition_message(:record)
80
+ yield
81
+ @state = :write
82
+ end
83
+
84
+ def course_point(values)
85
+ raise "Can only write course points inside 'course_points' block" if @state != :course_points
86
+ write_data_message(:course_point, values)
87
+ end
88
+
89
+ def track_point(values)
90
+ raise "Can only write track points inside 'track_points' block" if @state != :track_points
91
+ write_data_message(:record, values)
92
+ end
93
+
94
+ protected
95
+
96
+ def write_definition_message(type)
97
+ write_data(RubyFit::MessageWriter.definition_message(type, local_num(type)))
98
+ end
99
+
100
+ def write_data_message(type, values)
101
+ write_data(RubyFit::MessageWriter.data_message(type, local_num(type), values))
102
+ end
103
+
104
+ def write_data(data)
105
+ @stream.write(data)
106
+ prev = @data_crc
107
+ @data_crc = RubyFit::CRC.update_crc(@data_crc, data)
108
+ end
109
+
110
+ def local_num(type)
111
+ result = @local_nums.index(type)
112
+ unless result
113
+ result = @local_nums.size
114
+ @local_nums << type
115
+ end
116
+ result
117
+ end
118
+ end
metadata CHANGED
@@ -1,15 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyfit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cullen King
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-11 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2018-02-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.13.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.13.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.9.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.9.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.3
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: awesome_print
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
13
69
  description: FIT files are binary, and as a result, are a pain to parse. This is
14
70
  a wrapper around the FIT SDK, which makes creating a stream based parser simple.
15
71
  email:
@@ -34,7 +90,11 @@ files:
34
90
  - ext/rubyfit/fit_ram.h
35
91
  - ext/rubyfit/rubyfit.c
36
92
  - lib/rubyfit.rb
93
+ - lib/rubyfit/helpers.rb
94
+ - lib/rubyfit/message_writer.rb
95
+ - lib/rubyfit/type.rb
37
96
  - lib/rubyfit/version.rb
97
+ - lib/rubyfit/writer.rb
38
98
  homepage: http://cullenking.com
39
99
  licenses: []
40
100
  metadata: {}
@@ -55,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
115
  version: '0'
56
116
  requirements: []
57
117
  rubyforge_project: rubyfit
58
- rubygems_version: 2.6.6
118
+ rubygems_version: 2.5.2.1
59
119
  signing_key:
60
120
  specification_version: 4
61
121
  summary: A stream based parser for FIT files.