rubyfit 0.0.6 → 0.0.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 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.