git-fit 0.7.9 → 0.8.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/git-fit.rb +44 -42
  3. data/lib/git_fit/cli/export.rb +8 -6
  4. data/lib/git_fit/cli/geo_cli.rb +10 -8
  5. data/lib/git_fit/cli/gh_cli.rb +24 -22
  6. data/lib/git_fit/cli/import_cli.rb +40 -38
  7. data/lib/git_fit/cli/install_cli.rb +10 -8
  8. data/lib/git_fit/cli/sync.rb +9 -7
  9. data/lib/git_fit/cli.rb +29 -27
  10. data/lib/git_fit/config.rb +42 -28
  11. data/lib/git_fit/config_template.rb +2 -0
  12. data/lib/git_fit/db/activity.rb +2 -0
  13. data/lib/git_fit/db/cli.rb +11 -9
  14. data/lib/git_fit/db/connection.rb +8 -6
  15. data/lib/git_fit/db/rebuild.rb +5 -3
  16. data/lib/git_fit/export/defaults.rb +14 -12
  17. data/lib/git_fit/export/json.rb +5 -3
  18. data/lib/git_fit/export.rb +4 -2
  19. data/lib/git_fit/fit/decoder.rb +9 -7
  20. data/lib/git_fit/fit.rb +3 -1
  21. data/lib/git_fit/geo/amap_client.rb +15 -13
  22. data/lib/git_fit/geo/coord_transform.rb +2 -0
  23. data/lib/git_fit/geo/division_detector.rb +31 -26
  24. data/lib/git_fit/geo/nominatim_client.rb +14 -12
  25. data/lib/git_fit/geo/polyline.rb +4 -2
  26. data/lib/git_fit/geo/reverse_geocode.rb +11 -9
  27. data/lib/git_fit/geo.rb +8 -6
  28. data/lib/git_fit/import/apple_health.rb +84 -82
  29. data/lib/git_fit/import/local_file.rb +45 -42
  30. data/lib/git_fit/import.rb +4 -2
  31. data/lib/git_fit/install/actions.rb +15 -13
  32. data/lib/git_fit/parser/base.rb +12 -10
  33. data/lib/git_fit/parser/fit.rb +11 -9
  34. data/lib/git_fit/parser/gpx.rb +17 -15
  35. data/lib/git_fit/parser/tcx.rb +22 -20
  36. data/lib/git_fit/parser.rb +6 -4
  37. data/lib/git_fit/privacy/polyline_filter.rb +6 -4
  38. data/lib/git_fit/source/base.rb +25 -23
  39. data/lib/git_fit/source/tally.rb +9 -7
  40. data/lib/git_fit/source.rb +4 -2
  41. data/lib/git_fit/sport_mapper.rb +23 -21
  42. data/lib/git_fit/sync/base.rb +29 -27
  43. data/lib/git_fit/sync/garmin.rb +5 -3
  44. data/lib/git_fit/sync/garmin_base.rb +145 -143
  45. data/lib/git_fit/sync/garmin_base_di.rb +42 -40
  46. data/lib/git_fit/sync/garmin_cn.rb +8 -6
  47. data/lib/git_fit/sync/igpsport.rb +43 -41
  48. data/lib/git_fit/sync/keep.rb +98 -96
  49. data/lib/git_fit/sync/lorem.rb +31 -29
  50. data/lib/git_fit/sync/runner.rb +14 -11
  51. data/lib/git_fit/sync/strava.rb +61 -52
  52. data/lib/git_fit/sync/xingzhe.rb +52 -50
  53. data/lib/git_fit/sync/xoss.rb +68 -66
  54. data/lib/git_fit/timezone/resolver.rb +5 -3
  55. data/lib/git_fit/util/tally.rb +10 -8
  56. data/lib/git_fit/util.rb +2 -0
  57. data/lib/git_fit/version.rb +3 -1
  58. metadata +1 -1
@@ -1,4 +1,6 @@
1
- require "time"
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
2
4
 
3
5
  module GitFit
4
6
  module Parser
@@ -69,7 +71,7 @@ module GitFit
69
71
  end
70
72
 
71
73
  def default_sport
72
- "other"
74
+ 'other'
73
75
  end
74
76
 
75
77
  def simplify_polyline(points, max_points: 500)
@@ -83,13 +85,13 @@ module GitFit
83
85
  def normalize_sport_type(type)
84
86
  return default_sport if type.nil? || type.empty?
85
87
  mapping = {
86
- "running" => "run", "run" => "run",
87
- "cycling" => "ride", "biking" => "ride", "ride" => "ride",
88
- "hiking" => "hike", "hike" => "hike",
89
- "walking" => "walk", "walk" => "walk",
90
- "swimming" => "swim", "swim" => "swim",
91
- "skiing" => "ski", "ski" => "ski",
92
- "workout" => "workout", "other" => "other"
88
+ 'running' => 'run', 'run' => 'run',
89
+ 'cycling' => 'ride', 'biking' => 'ride', 'ride' => 'ride',
90
+ 'hiking' => 'hike', 'hike' => 'hike',
91
+ 'walking' => 'walk', 'walk' => 'walk',
92
+ 'swimming' => 'swim', 'swim' => 'swim',
93
+ 'skiing' => 'ski', 'ski' => 'ski',
94
+ 'workout' => 'workout', 'other' => 'other'
93
95
  }
94
96
  mapping[type.downcase.strip] || default_sport
95
97
  end
@@ -110,7 +112,7 @@ module GitFit
110
112
  average_heartrate: data[:average_heartrate]&.round(1),
111
113
  average_speed: data[:distance] && data[:moving_time].to_f.positive? ? (data[:distance].to_f / data[:moving_time]).round(2) : data[:average_speed],
112
114
  elevation_gain: data[:elevation_gain]&.round(1),
113
- source: @source_name || data[:source]
115
+ source: @source_name || data[:source],
114
116
  }
115
117
  end
116
118
 
@@ -1,6 +1,8 @@
1
- require "json"
2
- require "securerandom"
3
- require "time"
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'securerandom'
5
+ require 'time'
4
6
 
5
7
  module GitFit
6
8
  module Parser
@@ -24,7 +26,7 @@ module GitFit
24
26
  sport_type = GitFit::FIT::Decoder.sport(path)
25
27
 
26
28
  [build_activity_attrs({
27
- run_id: build_run_id("fit", SecureRandom.hex(8)),
29
+ run_id: build_run_id('fit', SecureRandom.hex(8)),
28
30
  name: nil,
29
31
  distance: total_distance(points),
30
32
  moving_time: moving_time_from_points(points),
@@ -37,19 +39,19 @@ module GitFit
37
39
  summary_polyline: simplify_polyline(points.map { |p| [p[0], p[1]] }),
38
40
  average_heartrate: average_heartrate(points),
39
41
  elevation_gain: elevation_gain(points),
40
- source: "fit"
42
+ source: 'fit',
41
43
  })]
42
44
  end
43
45
 
44
46
  private
45
47
 
46
48
  def record_to_point(rec)
47
- lat = rec["positionLat"]
48
- lon = rec["positionLong"]
49
+ lat = rec['positionLat']
50
+ lon = rec['positionLong']
49
51
  return nil unless lat && lon
50
- ts = rec["timestamp"]
52
+ ts = rec['timestamp']
51
53
  time = ts.is_a?(Numeric) ? FIT_EPOCH + ts : parse_time(ts)
52
- [lat, lon, rec["altitude"], time, rec["heartRate"]]
54
+ [lat, lon, rec['altitude'], time, rec['heartRate']]
53
55
  end
54
56
 
55
57
  def average_heartrate(points)
@@ -1,15 +1,17 @@
1
- require "time"
2
- require "nokogiri"
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'nokogiri'
3
5
 
4
6
  module GitFit
5
7
  module Parser
6
8
  class GPX < Base
7
- NS = { gpx: "http://www.topografix.com/GPX/1/1" }.freeze
9
+ NS = { gpx: 'http://www.topografix.com/GPX/1/1' }.freeze
8
10
 
9
11
  def call(input)
10
12
  doc = input.is_a?(String) ? Nokogiri::XML(input) : input
11
13
  doc.remove_namespaces!
12
- tracks = doc.xpath("//trk")
14
+ tracks = doc.xpath('//trk')
13
15
  return [] if tracks.empty?
14
16
  tracks.filter_map { |trk| parse_track(trk) }
15
17
  end
@@ -17,8 +19,8 @@ module GitFit
17
19
  private
18
20
 
19
21
  def parse_track(trk)
20
- name = trk.at_xpath("name")&.text
21
- segments = trk.xpath("trkseg")
22
+ name = trk.at_xpath('name')&.text
23
+ segments = trk.xpath('trkseg')
22
24
  return nil if segments.empty?
23
25
  points = segments.flat_map { |seg| parse_segment(seg) }
24
26
  return nil if points.empty?
@@ -26,7 +28,7 @@ module GitFit
26
28
  start_date = times.min
27
29
  end_date = times.max
28
30
  data = {
29
- run_id: build_run_id("gpx", start_date&.strftime("%Y%m%dT%H%M%S") || SecureRandom.hex(4)),
31
+ run_id: build_run_id('gpx', start_date&.strftime('%Y%m%dT%H%M%S') || SecureRandom.hex(4)),
30
32
  name: name,
31
33
  distance: total_distance(points),
32
34
  moving_time: moving_time_from_points(points),
@@ -39,27 +41,27 @@ module GitFit
39
41
  summary_polyline: simplify_polyline(points.map { |p| [p[0], p[1]] }),
40
42
  average_heartrate: average_heartrate(points),
41
43
  elevation_gain: elevation_gain(points),
42
- source: "gpx"
44
+ source: 'gpx',
43
45
  }
44
46
  build_activity_attrs(data)
45
47
  end
46
48
 
47
49
  def parse_segment(seg)
48
- seg.xpath("trkpt").filter_map do |pt|
49
- lat = pt["lat"]&.to_f
50
- lon = pt["lon"]&.to_f
50
+ seg.xpath('trkpt').filter_map do |pt|
51
+ lat = pt['lat']&.to_f
52
+ lon = pt['lon']&.to_f
51
53
  next unless lat && lon
52
- elev = pt.at_xpath("ele")&.text&.to_f
53
- time = parse_time(pt.at_xpath("time")&.text)
54
+ elev = pt.at_xpath('ele')&.text&.to_f
55
+ time = parse_time(pt.at_xpath('time')&.text)
54
56
  hr = parse_heartrate(pt)
55
57
  [lat, lon, elev, time, hr]
56
58
  end
57
59
  end
58
60
 
59
61
  def parse_heartrate(pt)
60
- ext = pt.at_xpath("extensions")
62
+ ext = pt.at_xpath('extensions')
61
63
  return nil unless ext
62
- hr = ext.at_xpath(".//hr")
64
+ hr = ext.at_xpath('.//hr')
63
65
  hr&.text&.to_f
64
66
  end
65
67
 
@@ -1,14 +1,16 @@
1
- require "time"
2
- require "nokogiri"
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'nokogiri'
3
5
 
4
6
  module GitFit
5
7
  module Parser
6
8
  class TCX < Base
7
- NS = { tcx: "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2" }.freeze
9
+ NS = { tcx: 'http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2' }.freeze
8
10
 
9
11
  def call(input)
10
12
  doc = input.is_a?(String) ? Nokogiri::XML(input) : input
11
- activities = doc.xpath("//tcx:Activities/tcx:Activity", NS)
13
+ activities = doc.xpath('//tcx:Activities/tcx:Activity', NS)
12
14
  return [] if activities.empty?
13
15
  activities.filter_map { |act| parse_activity(act) }
14
16
  end
@@ -16,8 +18,8 @@ module GitFit
16
18
  private
17
19
 
18
20
  def parse_activity(act)
19
- sport = act["Sport"]
20
- laps = act.xpath("tcx:Lap", NS)
21
+ sport = act['Sport']
22
+ laps = act.xpath('tcx:Lap', NS)
21
23
  return nil if laps.empty?
22
24
  lap_data = laps.map { |lap| parse_lap(lap) }.compact
23
25
  return nil if lap_data.empty?
@@ -30,8 +32,8 @@ module GitFit
30
32
  times = all_points.map { |p| p[3] }.compact
31
33
  start_date = times.min
32
34
  data = {
33
- run_id: build_run_id("tcx", start_date&.strftime("%Y%m%dT%H%M%S") || SecureRandom.hex(4)),
34
- name: act.at_xpath("tcx:Notes", NS)&.text,
35
+ run_id: build_run_id('tcx', start_date&.strftime('%Y%m%dT%H%M%S') || SecureRandom.hex(4)),
36
+ name: act.at_xpath('tcx:Notes', NS)&.text,
35
37
  distance: total_distance,
36
38
  moving_time: total_moving_time,
37
39
  elapsed_time: total_elapsed_time,
@@ -43,7 +45,7 @@ module GitFit
43
45
  summary_polyline: simplify_polyline(all_points.map { |p| [p[0], p[1]] }),
44
46
  average_heartrate: avg_hr.empty? ? nil : (avg_hr.sum / avg_hr.size).round(1),
45
47
  elevation_gain: elevation_gain,
46
- source: "tcx"
48
+ source: 'tcx',
47
49
  }
48
50
  build_activity_attrs(data)
49
51
  end
@@ -51,9 +53,9 @@ module GitFit
51
53
  def parse_lap(lap)
52
54
  points = parse_track(lap)
53
55
  return nil if points.empty?
54
- distance = lap.at_xpath("tcx:DistanceMeters", NS)&.text&.to_f
55
- total_time = lap.at_xpath("tcx:TotalTimeSeconds", NS)&.text&.to_f
56
- avg_hr_node = lap.at_xpath("tcx:AverageHeartRateBpm/tcx:Value", NS)
56
+ distance = lap.at_xpath('tcx:DistanceMeters', NS)&.text&.to_f
57
+ total_time = lap.at_xpath('tcx:TotalTimeSeconds', NS)&.text&.to_f
58
+ avg_hr_node = lap.at_xpath('tcx:AverageHeartRateBpm/tcx:Value', NS)
57
59
  avg_hr = avg_hr_node&.text&.to_f
58
60
  elev_gain = points.length >= 2 ? elevation_gain(points) : 0.0
59
61
  moving_time = moving_time_from_points(points, threshold_s: 30)
@@ -63,17 +65,17 @@ module GitFit
63
65
  end
64
66
 
65
67
  def parse_track(lap)
66
- track = lap.at_xpath("tcx:Track", NS)
68
+ track = lap.at_xpath('tcx:Track', NS)
67
69
  return [] unless track
68
- track.xpath("tcx:Trackpoint", NS).filter_map do |tp|
69
- pos = tp.at_xpath("tcx:Position", NS)
70
+ track.xpath('tcx:Trackpoint', NS).filter_map do |tp|
71
+ pos = tp.at_xpath('tcx:Position', NS)
70
72
  next unless pos
71
- lat = pos.at_xpath("tcx:LatitudeDegrees", NS)&.text&.to_f
72
- lon = pos.at_xpath("tcx:LongitudeDegrees", NS)&.text&.to_f
73
+ lat = pos.at_xpath('tcx:LatitudeDegrees', NS)&.text&.to_f
74
+ lon = pos.at_xpath('tcx:LongitudeDegrees', NS)&.text&.to_f
73
75
  next unless lat && lon
74
- elev = tp.at_xpath("tcx:AltitudeMeters", NS)&.text&.to_f
75
- time = parse_time(tp.at_xpath("tcx:Time", NS)&.text)
76
- hr = tp.at_xpath("tcx:HeartRateBpm/tcx:Value", NS)&.text&.to_f
76
+ elev = tp.at_xpath('tcx:AltitudeMeters', NS)&.text&.to_f
77
+ time = parse_time(tp.at_xpath('tcx:Time', NS)&.text)
78
+ hr = tp.at_xpath('tcx:HeartRateBpm/tcx:Value', NS)&.text&.to_f
77
79
  [lat, lon, elev, time, hr]
78
80
  end
79
81
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GitFit
2
4
  module Parser
3
5
  end
4
6
  end
5
7
 
6
- require_relative "parser/base"
7
- require_relative "parser/gpx"
8
- require_relative "parser/tcx"
9
- require_relative "parser/fit"
8
+ require_relative 'parser/base'
9
+ require_relative 'parser/gpx'
10
+ require_relative 'parser/tcx'
11
+ require_relative 'parser/fit'
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GitFit
2
4
  module Privacy
3
5
  class PolylineFilter
4
6
  def initialize(config: {})
5
- @start_end_range = (config["start_end_range"] || 200).to_f / 1000.0
6
- @ignore_polyline = decode_polyline(config["polyline"]) if config["polyline"]
7
- @ignore_range = (config["range"] || 0).to_f / 1000.0
8
- @before_saving = config["before_saving"] || false
7
+ @start_end_range = (config['start_end_range'] || 200).to_f / 1000.0
8
+ @ignore_polyline = decode_polyline(config['polyline']) if config['polyline']
9
+ @ignore_range = (config['range'] || 0).to_f / 1000.0
10
+ @before_saving = config['before_saving'] || false
9
11
  end
10
12
 
11
13
  def before_saving?
@@ -1,4 +1,6 @@
1
- require "date"
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
2
4
 
3
5
  module GitFit
4
6
  module Source
@@ -45,28 +47,28 @@ module GitFit
45
47
 
46
48
  def build_progress_info(attrs)
47
49
  parts = []
48
- sd = attrs["start_date"] || attrs[:start_date]
50
+ sd = attrs['start_date'] || attrs[:start_date]
49
51
  if sd
50
52
  d = Date.parse(sd.to_s) rescue nil
51
53
  if d
52
- date_str = d.year == Date.today.year ? d.strftime("%m-%d") : d.strftime("%Y-%m-%d")
54
+ date_str = d.year == Date.today.year ? d.strftime('%m-%d') : d.strftime('%Y-%m-%d')
53
55
  parts << date_str
54
56
  end
55
57
  end
56
- type = attrs["sport_category"] || attrs[:sport_category]
58
+ type = attrs['sport_category'] || attrs[:sport_category]
57
59
  parts << type if type
58
- dist = attrs["distance"] || attrs[:distance]
60
+ dist = attrs['distance'] || attrs[:distance]
59
61
  if dist && dist.to_f > 0
60
62
  parts << "#{format('%.1f', dist.to_f / 1000)}km"
61
63
  end
62
- source = attrs["source"] || attrs[:source] || source_label
63
- name = attrs["name"] || attrs[:name]
64
+ source = attrs['source'] || attrs[:source] || source_label
65
+ name = attrs['name'] || attrs[:name]
64
66
  display_name = name.to_s.strip
65
67
  display_name = "#{source} Activity" if display_name.empty?
66
68
  chars = display_name.chars
67
- display_name = chars.first(30).join + "" if chars.size > 30
69
+ display_name = chars.first(30).join + '' if chars.size > 30
68
70
  parts << display_name
69
- parts.join(" | ") unless parts.empty?
71
+ parts.join(' | ') unless parts.empty?
70
72
  end
71
73
 
72
74
  protected
@@ -77,8 +79,8 @@ module GitFit
77
79
  end
78
80
 
79
81
  def raw_dir
80
- src = self.class.name.split("::").last.downcase
81
- File.join("data", "raw", src)
82
+ src = self.class.name.split('::').last.downcase
83
+ File.join('data', 'raw', src)
82
84
  end
83
85
 
84
86
  def raw_path(platform_id)
@@ -90,11 +92,11 @@ module GitFit
90
92
  end
91
93
 
92
94
  def std_dir
93
- src = self.class.name.split("::").last.downcase
94
- File.join("data", "std", src)
95
+ src = self.class.name.split('::').last.downcase
96
+ File.join('data', 'std', src)
95
97
  end
96
98
 
97
- def std_path(platform_id, ext = "json")
99
+ def std_path(platform_id, ext = 'json')
98
100
  File.join(std_dir, "#{platform_id}.#{ext}")
99
101
  end
100
102
 
@@ -104,7 +106,7 @@ module GitFit
104
106
  line = "#{source_label}: #{count}"
105
107
  line += "/#{total}" if total
106
108
  line += " #{info}" if info
107
- line += " "
109
+ line += ' '
108
110
  print "\r#{line}"
109
111
  $stdout.flush
110
112
  end
@@ -123,7 +125,7 @@ module GitFit
123
125
 
124
126
  def source_label
125
127
  name = self.class.name
126
- name ? name.split("::").last : "Source"
128
+ name ? name.split('::').last : 'Source'
127
129
  end
128
130
 
129
131
  def upsert_activity(attrs)
@@ -131,17 +133,17 @@ module GitFit
131
133
  now = Time.now.utc
132
134
 
133
135
  if @privacy && !@privacy.before_saving?
134
- attrs["summary_polyline"] = @privacy.filter(attrs["summary_polyline"])
136
+ attrs['summary_polyline'] = @privacy.filter(attrs['summary_polyline'])
135
137
  end
136
138
 
137
- existing = @db[:activities].where(run_id: attrs["run_id"]).first
139
+ existing = @db[:activities].where(run_id: attrs['run_id']).first
138
140
  if existing
139
- attrs.delete("created_at")
140
- attrs["updated_at"] = now
141
- @db[:activities].where(run_id: attrs["run_id"]).update(attrs)
141
+ attrs.delete('created_at')
142
+ attrs['updated_at'] = now
143
+ @db[:activities].where(run_id: attrs['run_id']).update(attrs)
142
144
  else
143
- attrs["created_at"] ||= now
144
- attrs["updated_at"] ||= now
145
+ attrs['created_at'] ||= now
146
+ attrs['updated_at'] ||= now
145
147
  @db[:activities].insert(attrs)
146
148
  end
147
149
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GitFit
2
4
  module Source
3
5
  class Tally
@@ -25,26 +27,26 @@ module GitFit
25
27
  return DIGITS[0] if n == 0
26
28
  return DIGITS[n] if n <= 10
27
29
 
28
- result = ""
30
+ result = ''
29
31
  thousands = n / 1000
30
32
  n %= 1000
31
- result += DIGITS[thousands] + "" if thousands > 0
33
+ result += DIGITS[thousands] + '' if thousands > 0
32
34
 
33
35
  hundreds = n / 100
34
36
  n %= 100
35
37
  if hundreds > 0
36
- result += DIGITS[hundreds] + ""
38
+ result += DIGITS[hundreds] + ''
37
39
  elsif thousands > 0 && n > 0
38
- result += ""
40
+ result += ''
39
41
  end
40
42
 
41
43
  tens = n / 10
42
44
  ones = n % 10
43
45
  if tens > 0
44
46
  join_ten = tens > 1 || !result.empty?
45
- result += (join_ten ? DIGITS[tens] : "") + ""
46
- elsif !result.empty? && tens == 0 && ones > 0 && !result.end_with?("")
47
- result += ""
47
+ result += (join_ten ? DIGITS[tens] : '') + ''
48
+ elsif !result.empty? && tens == 0 && ones > 0 && !result.end_with?('')
49
+ result += ''
48
50
  end
49
51
 
50
52
  result += DIGITS[ones] if ones > 0
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GitFit
2
4
  module Source
3
5
  end
4
6
  end
5
7
 
6
- require_relative "util/tally"
7
- require_relative "source/base"
8
+ require_relative 'util/tally'
9
+ require_relative 'source/base'
8
10
 
9
11
  GitFit::Source::Tally = GitFit::Util::Tally
@@ -1,28 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GitFit
2
4
  module SportMapper
3
5
  ALIASES = {
4
- "running" => "run", "trailrun" => "run", "trail_run" => "run",
5
- "virtualrun" => "run", "virtual_run" => "run",
6
- "indoorrun" => "run", "indoor_run" => "run",
7
- "cycling" => "ride", "biking" => "ride",
8
- "mountainbike" => "ride", "gravelride" => "ride", "ebikeride" => "ride",
9
- "virtualride" => "ride", "virtual_ride" => "ride",
10
- "indoor_ride" => "ride", "indoorride" => "ride",
11
- "hiking" => "hike",
12
- "walking" => "walk",
13
- "swimming" => "swim",
14
- "skiing" => "ski", "backcountryski" => "ski", "nordicski" => "ski",
15
- "strengthtraining" => "workout"
6
+ 'running' => 'run', 'trailrun' => 'run', 'trail_run' => 'run',
7
+ 'virtualrun' => 'run', 'virtual_run' => 'run',
8
+ 'indoorrun' => 'run', 'indoor_run' => 'run',
9
+ 'cycling' => 'ride', 'biking' => 'ride',
10
+ 'mountainbike' => 'ride', 'gravelride' => 'ride', 'ebikeride' => 'ride',
11
+ 'virtualride' => 'ride', 'virtual_ride' => 'ride',
12
+ 'indoor_ride' => 'ride', 'indoorride' => 'ride',
13
+ 'hiking' => 'hike',
14
+ 'walking' => 'walk',
15
+ 'swimming' => 'swim',
16
+ 'skiing' => 'ski', 'backcountryski' => 'ski', 'nordicski' => 'ski',
17
+ 'strengthtraining' => 'workout'
16
18
  }.freeze
17
19
 
18
20
  KEYWORDS = [
19
- [/\b(?:run|jog|sprint|marathon)\w*\b/i, "run"],
20
- [/\b(?:ride|cycl|bike|mountain\.?bike|gravel|ebike)\w*\b/i, "ride"],
21
- [/\b(?:hike|climb|mountaineer|trek)\w*\b/i, "hike"],
22
- [/\b(?:walk|stroll)\w*\b/i, "walk"],
23
- [/\b(?:swim|pool|laps?)\w*\b/i, "swim"],
24
- [/\b(?:ski|snowboard|nordic)\w*\b/i, "ski"],
25
- [/\b(?:workout|train|gym|fitness|yoga|pilates|crossfit|elliptical|strength)\w*\b/i, "workout"]
21
+ [/\b(?:run|jog|sprint|marathon)\w*\b/i, 'run'],
22
+ [/\b(?:ride|cycl|bike|mountain\.?bike|gravel|ebike)\w*\b/i, 'ride'],
23
+ [/\b(?:hike|climb|mountaineer|trek)\w*\b/i, 'hike'],
24
+ [/\b(?:walk|stroll)\w*\b/i, 'walk'],
25
+ [/\b(?:swim|pool|laps?)\w*\b/i, 'swim'],
26
+ [/\b(?:ski|snowboard|nordic)\w*\b/i, 'ski'],
27
+ [/\b(?:workout|train|gym|fitness|yoga|pilates|crossfit|elliptical|strength)\w*\b/i, 'workout']
26
28
  ].freeze
27
29
 
28
30
  LEV_REFERENCES = %w[
@@ -36,13 +38,13 @@ module GitFit
36
38
  LEV_THRESHOLD = 3
37
39
 
38
40
  def self.canonicalize(str)
39
- return "other" if str.nil? || str.empty?
41
+ return 'other' if str.nil? || str.empty?
40
42
  key = str.to_s.downcase.strip
41
43
  return ALIASES[key] if ALIASES.key?(key)
42
44
  KEYWORDS.each { |regex, sport| return sport if regex.match?(str) }
43
45
  best = lev_match(key)
44
46
  return best if best
45
- "other"
47
+ 'other'
46
48
  end
47
49
 
48
50
  def self.lev_match(str)