tracklib 0.1.7 → 0.3.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.
data/src/rwtfile.rs ADDED
@@ -0,0 +1,429 @@
1
+ use lazy_static::lazy_static;
2
+ use rutie::{
3
+ class, methods, wrappable_struct, AnyObject, Array, Boolean, Class, Encoding, Float, Hash,
4
+ Integer, Module, Object, RString, VM,
5
+ };
6
+ use rutie_serde::{ruby_class, rutie_serde_methods};
7
+ use std::collections::HashMap;
8
+ use std::convert::TryFrom;
9
+ use std::io::BufWriter;
10
+ use tracklib::{parse_rwtf, DataField, RWTFMetadata, RWTFile, TrackType};
11
+ use super::polyline;
12
+ use super::surface;
13
+
14
+ fn any_to_float(o: AnyObject) -> f64 {
15
+ match o.try_convert_to::<Float>() {
16
+ Ok(f) => f.to_f64(),
17
+ Err(float_e) => o
18
+ .try_convert_to::<Integer>()
19
+ .map_err(|_| VM::raise_ex(float_e))
20
+ .unwrap()
21
+ .to_i64() as f64,
22
+ }
23
+ }
24
+
25
+ fn any_to_int(o: AnyObject) -> i64 {
26
+ match o.try_convert_to::<Integer>() {
27
+ Ok(i) => i.to_i64(),
28
+ Err(int_e) => o
29
+ .try_convert_to::<Float>()
30
+ .map_err(|_| VM::raise_ex(int_e))
31
+ .unwrap()
32
+ .to_f64() as i64,
33
+ }
34
+ }
35
+
36
+ fn any_to_str(o: AnyObject) -> String {
37
+ o.try_convert_to::<RString>()
38
+ .map_err(|e| VM::raise_ex(e))
39
+ .unwrap()
40
+ .to_string()
41
+ }
42
+
43
+ fn any_to_bool(o: AnyObject) -> bool {
44
+ o.try_convert_to::<Boolean>()
45
+ .map_err(|e| VM::raise_ex(e))
46
+ .unwrap()
47
+ .to_bool()
48
+ }
49
+
50
+ fn any_to_ids(o: AnyObject) -> Vec<u64> {
51
+ o.try_convert_to::<Array>()
52
+ .map_err(|e| VM::raise_ex(e))
53
+ .unwrap()
54
+ .into_iter()
55
+ .map(|ele| {
56
+ ele.try_convert_to::<Integer>()
57
+ .map_err(|e| VM::raise_ex(e))
58
+ .unwrap()
59
+ .to_u64()
60
+ })
61
+ .collect()
62
+ }
63
+
64
+ #[derive(Debug, Copy, Clone)]
65
+ enum ColumnType {
66
+ Numbers,
67
+ LongFloat,
68
+ ShortFloat,
69
+ Base64,
70
+ String,
71
+ Bool,
72
+ IDs,
73
+ }
74
+
75
+ impl ColumnType {
76
+ fn from_str(name: &str) -> Option<Self> {
77
+ match name {
78
+ "Number" => Some(ColumnType::Numbers),
79
+ "LongFloat" => Some(ColumnType::LongFloat),
80
+ "ShortFloat" => Some(ColumnType::ShortFloat),
81
+ "Base64" => Some(ColumnType::Base64),
82
+ "String" => Some(ColumnType::String),
83
+ "Bool" => Some(ColumnType::Bool),
84
+ "IDs" => Some(ColumnType::IDs),
85
+ _ => None,
86
+ }
87
+ }
88
+
89
+ fn exponent(&self) -> u8 {
90
+ match self {
91
+ ColumnType::Numbers => 48,
92
+ ColumnType::LongFloat => 24,
93
+ ColumnType::ShortFloat => 38,
94
+ _ => {
95
+ VM::raise(
96
+ Class::from_existing("Exception"),
97
+ &format!("can't handle numeric value for non-numeric field"),
98
+ );
99
+ unreachable!();
100
+ }
101
+ }
102
+ }
103
+
104
+ fn max_integer(&self) -> i64 {
105
+ 2i64.pow(u32::from(self.exponent()))
106
+ }
107
+
108
+ fn max_float(&self) -> f64 {
109
+ 2f64.powi(i32::from(self.exponent()))
110
+ }
111
+ }
112
+
113
+ fn convert_config(config: Hash) -> HashMap<String, ColumnType> {
114
+ let mut hm = HashMap::new();
115
+
116
+ config.each(|rwtf_type_name, array_of_field_names| {
117
+ let type_name_obj = rwtf_type_name
118
+ .try_convert_to::<RString>()
119
+ .map_err(|e| VM::raise_ex(e))
120
+ .unwrap();
121
+ let type_name_str = type_name_obj.to_str();
122
+
123
+ if let Some(column_type) = ColumnType::from_str(type_name_str) {
124
+ let array_obj = array_of_field_names
125
+ .try_convert_to::<Array>()
126
+ .map_err(|e| VM::raise_ex(e))
127
+ .unwrap();
128
+
129
+ for field_name in array_obj.into_iter() {
130
+ let field_name_obj = field_name
131
+ .try_convert_to::<RString>()
132
+ .map_err(|e| VM::raise_ex(e))
133
+ .unwrap();
134
+
135
+ hm.insert(field_name_obj.to_string(), column_type);
136
+ }
137
+ } else {
138
+ VM::raise(
139
+ Class::from_existing("Exception"),
140
+ &format!("unknown rwtf_field_config type: {}", type_name_str),
141
+ );
142
+ unreachable!();
143
+ }
144
+ });
145
+
146
+ hm
147
+ }
148
+
149
+ fn is_empty_string(v: &AnyObject) -> bool {
150
+ match v.try_convert_to::<RString>() {
151
+ Ok(s) => s.to_str().is_empty(),
152
+ Err(_) => false,
153
+ }
154
+ }
155
+
156
+ fn is_empty_array(v: &AnyObject) -> bool {
157
+ match v.try_convert_to::<Array>() {
158
+ Ok(a) => a.length() == 0,
159
+ Err(_) => false,
160
+ }
161
+ }
162
+
163
+ fn is_number_and_too_large(v: &AnyObject, field_type: ColumnType) -> bool {
164
+ // First we have to try to cast `v` to a numeric type (Integer or Float)
165
+ // and then call to_i64/f64(). This will raise an exception if the number
166
+ // is too large to turn into a primitive.
167
+ let is_number_result = VM::protect(|| {
168
+ match v.try_convert_to::<Integer>() {
169
+ Ok(i) => {
170
+ let _ = i.to_i64(); // force a conversion
171
+ Boolean::new(true)
172
+ }
173
+ Err(_) => match v.try_convert_to::<Float>() {
174
+ Ok(f) => {
175
+ let _ = f.to_f64(); // force a conversion
176
+ Boolean::new(true)
177
+ }
178
+ Err(_) => Boolean::new(false),
179
+ },
180
+ }
181
+ .to_any_object()
182
+ });
183
+
184
+ match is_number_result {
185
+ Ok(is_number) => {
186
+ // Here we know that no exception was raised during the attempted primitive conversion.
187
+ // We also know that `is_number` is a Boolean, so this unsafe cast is fine.
188
+ if unsafe { is_number.to::<Boolean>().to_bool() } {
189
+ match v.try_convert_to::<Integer>() {
190
+ Ok(i) => i.to_i64().abs() > field_type.max_integer(),
191
+ Err(_) => match v.try_convert_to::<Float>() {
192
+ Ok(f) => f.to_f64().abs() > field_type.max_float(),
193
+ Err(_) => false,
194
+ },
195
+ }
196
+ } else {
197
+ false
198
+ }
199
+ }
200
+ Err(_) => {
201
+ VM::clear_error_info(); // clear ruby VM error register
202
+ true // this IS a number and it IS too large
203
+ }
204
+ }
205
+ }
206
+
207
+ fn add_points(
208
+ source: &Hash,
209
+ section_points_config: &HashMap<String, ColumnType>,
210
+ section_type: &str,
211
+ mut callback: impl FnMut(usize, &str, DataField),
212
+ ) {
213
+ let maybe_section_points = source
214
+ .at(&RString::new_utf8(section_type))
215
+ .try_convert_to::<Array>();
216
+
217
+ if let Ok(section_points) = maybe_section_points {
218
+ for (i, maybe_point) in section_points.into_iter().enumerate() {
219
+ let point = maybe_point
220
+ .try_convert_to::<Hash>()
221
+ .map_err(|e| VM::raise_ex(e))
222
+ .unwrap();
223
+
224
+ point.each(|k: AnyObject, v: AnyObject| {
225
+ let field_name_obj = k
226
+ .try_convert_to::<RString>()
227
+ .map_err(|e| VM::raise_ex(e))
228
+ .unwrap();
229
+ let name = field_name_obj.to_str();
230
+
231
+ if !name.is_empty() {
232
+ if let Some(field_type) = section_points_config.get(name) {
233
+ if !v.is_nil()
234
+ && !is_empty_string(&v)
235
+ && !is_empty_array(&v)
236
+ && !is_number_and_too_large(&v, *field_type)
237
+ {
238
+ let data = match field_type {
239
+ ColumnType::LongFloat => DataField::LongFloat(any_to_float(v)),
240
+ ColumnType::ShortFloat => DataField::ShortFloat(any_to_float(v)),
241
+ ColumnType::Numbers => DataField::Number(any_to_int(v)),
242
+ ColumnType::Base64 => {
243
+ DataField::Base64(any_to_str(v).replace("\n", ""))
244
+ }
245
+ ColumnType::String => DataField::String(any_to_str(v)),
246
+ ColumnType::Bool => DataField::Bool(any_to_bool(v)),
247
+ ColumnType::IDs => DataField::IDs(any_to_ids(v)),
248
+ };
249
+
250
+ callback(i, name, data);
251
+ }
252
+ } else {
253
+ VM::raise(
254
+ Module::from_existing("Tracklib").get_nested_class("UnknownFieldError"),
255
+ &format!("unknown {} field: {}", section_type, name),
256
+ );
257
+ unreachable!();
258
+ }
259
+ }
260
+ });
261
+ }
262
+ }
263
+ }
264
+
265
+ pub struct Inner {
266
+ inner: RWTFile,
267
+ }
268
+
269
+ wrappable_struct!(Inner, InnerWrapper, INNER_WRAPPER);
270
+
271
+ class!(RubyRWTFile);
272
+
273
+ methods!(
274
+ RubyRWTFile,
275
+ itself,
276
+ fn rwtf_from_bytes(bytes: RString) -> AnyObject {
277
+ let source = bytes.map_err(|e| VM::raise_ex(e)).unwrap();
278
+ let (_, rwtf) = parse_rwtf(source.to_bytes_unchecked())
279
+ .map_err(|e| VM::raise(Class::from_existing("Exception"), &format!("{}", e)))
280
+ .unwrap();
281
+ let inner = Inner { inner: rwtf };
282
+
283
+ Class::from_existing("RWTFile").wrap_data(inner, &*INNER_WRAPPER)
284
+ }
285
+
286
+ fn rwtf_to_bytes() -> RString {
287
+ let mut writer = BufWriter::new(Vec::new());
288
+ itself
289
+ .get_data(&*INNER_WRAPPER)
290
+ .inner
291
+ .write(&mut writer)
292
+ .map_err(|e| VM::raise(Class::from_existing("Exception"), &format!("{}", e)))
293
+ .unwrap();
294
+
295
+ let encoding = Encoding::find("ASCII-8BIT")
296
+ .map_err(|e| VM::raise_ex(e))
297
+ .unwrap();
298
+
299
+ let buf = writer
300
+ .into_inner()
301
+ .map_err(|e| VM::raise(Class::from_existing("Exception"), &format!("{}", e)))
302
+ .unwrap();
303
+
304
+ RString::from_bytes(&buf, &encoding)
305
+ }
306
+
307
+ fn rwtf_from_hash(input: Hash, config_input: Hash, metadata: Hash) -> AnyObject {
308
+ let source = input.map_err(|e| VM::raise_ex(e)).unwrap();
309
+ let config = config_input.map_err(|e| VM::raise_ex(e)).unwrap();
310
+ let track_points_config = convert_config(
311
+ config
312
+ .at(&RString::new_utf8("track_points"))
313
+ .try_convert_to::<Hash>()
314
+ .map_err(|e| VM::raise_ex(e))
315
+ .unwrap(),
316
+ );
317
+ let course_points_config = convert_config(
318
+ config
319
+ .at(&RString::new_utf8("course_points"))
320
+ .try_convert_to::<Hash>()
321
+ .map_err(|e| VM::raise_ex(e))
322
+ .unwrap(),
323
+ );
324
+
325
+ let mut rwtf = if let Some(md) = metadata.ok() {
326
+ let tt_metadata = md
327
+ .at(&RString::new_utf8("track_type"))
328
+ .try_convert_to::<Hash>()
329
+ .map_err(|e| VM::raise_ex(e))
330
+ .unwrap();
331
+
332
+ let track_type = tt_metadata
333
+ .at(&RString::new_utf8("type"))
334
+ .try_convert_to::<RString>()
335
+ .map_err(|e| VM::raise_ex(e))
336
+ .unwrap();
337
+
338
+ let id = u32::try_from(
339
+ tt_metadata
340
+ .at(&RString::new_utf8("id"))
341
+ .try_convert_to::<Integer>()
342
+ .map_err(|e| VM::raise_ex(e))
343
+ .unwrap()
344
+ .to_u64(),
345
+ )
346
+ .map_err(|e| VM::raise(Class::from_existing("Exception"), &format!("{}", e)))
347
+ .unwrap();
348
+
349
+ let tt = match track_type.to_str() {
350
+ "trip" => TrackType::Trip(id),
351
+ "route" => TrackType::Route(id),
352
+ "segment" => TrackType::Segment(id),
353
+ _ => {
354
+ VM::raise(
355
+ Class::from_existing("Exception"),
356
+ &format!("unknown track_type metadata: {}", track_type.to_str()),
357
+ );
358
+ unreachable!();
359
+ }
360
+ };
361
+
362
+ RWTFile::with_track_type(tt)
363
+ } else {
364
+ RWTFile::new()
365
+ };
366
+
367
+ add_points(
368
+ &source,
369
+ &track_points_config,
370
+ "track_points",
371
+ |i, name, data| {
372
+ rwtf.add_track_point(i, name, data)
373
+ .map_err(|e| VM::raise(Class::from_existing("Exception"), &format!("{}", e)))
374
+ .unwrap();
375
+ },
376
+ );
377
+
378
+ add_points(
379
+ &source,
380
+ &course_points_config,
381
+ "course_points",
382
+ |i, name, data| {
383
+ rwtf.add_course_point(i, name, data)
384
+ .map_err(|e| VM::raise(Class::from_existing("Exception"), &format!("{}", e)))
385
+ .unwrap();
386
+ },
387
+ );
388
+
389
+ let inner = Inner { inner: rwtf };
390
+ Class::from_existing("RWTFile").wrap_data(inner, &*INNER_WRAPPER)
391
+ }
392
+
393
+ fn rwtf_simplify_track_points(surface_mapping: surface::RubySurfaceMapping,
394
+ tolerance: Float,
395
+ encode_options: polyline::RubyFieldEncodeOptionsVec) -> RString {
396
+ let track_points = &itself.get_data(&*INNER_WRAPPER).inner.track_points;
397
+ let surface_mapping_container = surface_mapping.map_err(|e| VM::raise_ex(e)).unwrap();
398
+ let mapping = surface_mapping_container.inner();
399
+ let tol = tolerance.map_err(|e| VM::raise_ex(e)).unwrap().to_f64();
400
+ let encode_options_container = encode_options.map_err(|e| VM::raise_ex(e)).unwrap();
401
+ let enc_opts = encode_options_container.inner();
402
+
403
+ RString::new_utf8(&track_points.simplify_and_encode(mapping, tol, enc_opts))
404
+ }
405
+
406
+ fn rwtf_inspect() -> RString {
407
+ let rwtf = &itself.get_data(&*INNER_WRAPPER).inner;
408
+
409
+ RString::new_utf8(&format!(
410
+ "RWTFile<track_points: {}, course_points: {}>",
411
+ rwtf.track_points.len(),
412
+ rwtf.course_points.len()
413
+ ))
414
+ }
415
+ );
416
+
417
+ rutie_serde_methods!(
418
+ RubyRWTFile,
419
+ itself,
420
+ ruby_class!(Exception),
421
+
422
+ fn rwtf_to_hash() -> &RWTFile {
423
+ &itself.get_data(&*INNER_WRAPPER).inner
424
+ }
425
+
426
+ fn rwtf_metadata() -> &RWTFMetadata {
427
+ &itself.get_data(&*INNER_WRAPPER).inner.metadata()
428
+ }
429
+ );
data/src/surface.rs ADDED
@@ -0,0 +1,143 @@
1
+ use lazy_static::lazy_static;
2
+ use rutie::{
3
+ class, methods, wrappable_struct, AnyObject, Array, Class, Float, Integer, NilClass, Object,
4
+ RString, VerifiedObject, VM,
5
+ };
6
+ use tracklib::{RoadClassMapping, SurfaceMapping};
7
+
8
+ pub struct RoadClassInner {
9
+ inner: RoadClassMapping,
10
+ }
11
+
12
+ wrappable_struct!(
13
+ RoadClassInner,
14
+ RoadClassInnerWrapper,
15
+ ROAD_CLASS_INNER_WRAPPER
16
+ );
17
+
18
+ class!(RubyRoadClassMapping);
19
+
20
+ methods!(
21
+ RubyRoadClassMapping,
22
+ itself,
23
+
24
+ fn road_class_mapping_new(bbox_array: Array) -> AnyObject {
25
+ let bbox_vec: Vec<f64> = bbox_array
26
+ .map_err(|e| VM::raise_ex(e))
27
+ .unwrap()
28
+ .into_iter()
29
+ .map(|ele| match ele.try_convert_to::<Float>() {
30
+ Ok(f) => f.to_f64(),
31
+ Err(float_e) => ele
32
+ .try_convert_to::<Integer>()
33
+ .map_err(|_| VM::raise_ex(float_e))
34
+ .unwrap()
35
+ .to_i32()
36
+ .into(),
37
+ })
38
+ .collect();
39
+ if bbox_vec.len() != 4 {
40
+ VM::raise(
41
+ Class::from_existing("Exception"),
42
+ "BBOX Array len must be 4",
43
+ );
44
+ }
45
+ let bbox = [bbox_vec[0], bbox_vec[1], bbox_vec[2], bbox_vec[3]];
46
+
47
+ let inner = RoadClassInner {
48
+ inner: tracklib::RoadClassMapping::new(bbox),
49
+ };
50
+
51
+ Class::from_existing("TracklibRoadClassMapping").wrap_data(inner, &*ROAD_CLASS_INNER_WRAPPER)
52
+ }
53
+
54
+ fn road_class_mapping_add_road_class(road_class_id: Integer, surface_id: Integer) -> NilClass {
55
+ let rc_id = road_class_id.map_err(|e| VM::raise_ex(e)).unwrap().to_i64();
56
+ let s_id = surface_id.map_err(|e| VM::raise_ex(e)).unwrap().to_i64();
57
+ let mapping = &mut itself.get_data_mut(&*ROAD_CLASS_INNER_WRAPPER).inner;
58
+ mapping.add_road_class(rc_id, s_id);
59
+
60
+ NilClass::new()
61
+ }
62
+
63
+ fn road_class_mapping_to_s() -> RString {
64
+ let mapping = &itself.get_data(&*ROAD_CLASS_INNER_WRAPPER).inner;
65
+
66
+ RString::new_utf8(&format!("{:?}", mapping))
67
+ }
68
+ );
69
+
70
+ impl VerifiedObject for RubyRoadClassMapping {
71
+ fn is_correct_type<T: Object>(object: &T) -> bool {
72
+ Class::from_existing("TracklibRoadClassMapping").case_equals(object)
73
+ }
74
+
75
+ fn error_message() -> &'static str {
76
+ "Error converting to RoadClassMapping"
77
+ }
78
+ }
79
+
80
+ pub struct SurfaceInner {
81
+ inner: SurfaceMapping,
82
+ }
83
+
84
+ wrappable_struct!(SurfaceInner, SurfaceInnerWrapper, SURFACE_INNER_WRAPPER);
85
+
86
+ class!(RubySurfaceMapping);
87
+
88
+ methods!(
89
+ RubySurfaceMapping,
90
+ itself,
91
+
92
+ fn surface_mapping_new(unknown_surface_id: Integer) -> AnyObject {
93
+ let id = unknown_surface_id
94
+ .map_err(|e| VM::raise_ex(e))
95
+ .unwrap()
96
+ .to_i64();
97
+ let inner = SurfaceInner {
98
+ inner: tracklib::SurfaceMapping::new(id),
99
+ };
100
+
101
+ Class::from_existing("TracklibSurfaceMapping").wrap_data(inner, &*SURFACE_INNER_WRAPPER)
102
+ }
103
+
104
+ fn surface_mapping_add_surface(surface_id: Integer, group: RString) -> NilClass {
105
+ let id = surface_id.map_err(|e| VM::raise_ex(e)).unwrap().to_i64();
106
+ let group_name = group.map_err(|e| VM::raise_ex(e)).unwrap().to_string();
107
+ let mapping = &mut itself.get_data_mut(&*SURFACE_INNER_WRAPPER).inner;
108
+ mapping.add_surface(id, group_name);
109
+
110
+ NilClass::new()
111
+ }
112
+
113
+ fn surface_mapping_add_road_class_mapping(road_class_mapping: RubyRoadClassMapping) -> NilClass {
114
+ let rcm = road_class_mapping.map_err(|e| VM::raise_ex(e)).unwrap();
115
+ let mapping = &mut itself.get_data_mut(&*SURFACE_INNER_WRAPPER).inner;
116
+ let road_class = &rcm.get_data(&*ROAD_CLASS_INNER_WRAPPER).inner;
117
+ mapping.add_road_class_mapping(road_class.clone());
118
+
119
+ NilClass::new()
120
+ }
121
+
122
+ fn surface_mapping_to_s() -> RString {
123
+ let mapping = &itself.get_data(&*SURFACE_INNER_WRAPPER).inner;
124
+
125
+ RString::new_utf8(&format!("{:?}", mapping))
126
+ }
127
+ );
128
+
129
+ impl RubySurfaceMapping {
130
+ pub fn inner(&self) -> &SurfaceMapping {
131
+ &self.get_data(&*SURFACE_INNER_WRAPPER).inner
132
+ }
133
+ }
134
+
135
+ impl VerifiedObject for RubySurfaceMapping {
136
+ fn is_correct_type<T: Object>(object: &T) -> bool {
137
+ Class::from_existing("TracklibSurfaceMapping").case_equals(object)
138
+ }
139
+
140
+ fn error_message() -> &'static str {
141
+ "Error converting to SurfaceMapping"
142
+ }
143
+ }
data/tracklib.gemspec CHANGED
@@ -20,7 +20,10 @@ Gem::Specification.new do |spec|
20
20
  "lib/tracklib/version.rb",
21
21
  "Cargo.toml",
22
22
  "Cargo.lock",
23
- "src/lib.rs"]
23
+ "src/lib.rs",
24
+ "src/polyline.rs",
25
+ "src/rwtfile.rs",
26
+ "src/surface.rs"]
24
27
 
25
28
  spec.require_paths = ["lib"]
26
29
  spec.extensions = ["ext/Rakefile"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tracklib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Larkin
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-27 00:00:00.000000000 Z
11
+ date: 1980-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -82,13 +82,16 @@ files:
82
82
  - lib/tracklib.rb
83
83
  - lib/tracklib/version.rb
84
84
  - src/lib.rs
85
+ - src/polyline.rs
86
+ - src/rwtfile.rs
87
+ - src/surface.rs
85
88
  - tracklib.gemspec
86
89
  homepage: https://ridewithgps.com
87
90
  licenses:
88
91
  - Apache-2.0
89
92
  - MIT
90
93
  metadata: {}
91
- post_install_message:
94
+ post_install_message:
92
95
  rdoc_options: []
93
96
  require_paths:
94
97
  - lib
@@ -103,9 +106,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
106
  - !ruby/object:Gem::Version
104
107
  version: '0'
105
108
  requirements: []
106
- rubyforge_project:
107
- rubygems_version: 2.6.14.3
108
- signing_key:
109
+ rubygems_version: 3.2.16
110
+ signing_key:
109
111
  specification_version: 4
110
112
  summary: tracklib
111
113
  test_files: []