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.
- checksums.yaml +4 -4
- data/lib/git-fit.rb +44 -42
- data/lib/git_fit/cli/export.rb +8 -6
- data/lib/git_fit/cli/geo_cli.rb +10 -8
- data/lib/git_fit/cli/gh_cli.rb +24 -22
- data/lib/git_fit/cli/import_cli.rb +40 -38
- data/lib/git_fit/cli/install_cli.rb +10 -8
- data/lib/git_fit/cli/sync.rb +9 -7
- data/lib/git_fit/cli.rb +29 -27
- data/lib/git_fit/config.rb +42 -28
- data/lib/git_fit/config_template.rb +2 -0
- data/lib/git_fit/db/activity.rb +2 -0
- data/lib/git_fit/db/cli.rb +11 -9
- data/lib/git_fit/db/connection.rb +8 -6
- data/lib/git_fit/db/rebuild.rb +5 -3
- data/lib/git_fit/export/defaults.rb +14 -12
- data/lib/git_fit/export/json.rb +5 -3
- data/lib/git_fit/export.rb +4 -2
- data/lib/git_fit/fit/decoder.rb +9 -7
- data/lib/git_fit/fit.rb +3 -1
- data/lib/git_fit/geo/amap_client.rb +15 -13
- data/lib/git_fit/geo/coord_transform.rb +2 -0
- data/lib/git_fit/geo/division_detector.rb +31 -26
- data/lib/git_fit/geo/nominatim_client.rb +14 -12
- data/lib/git_fit/geo/polyline.rb +4 -2
- data/lib/git_fit/geo/reverse_geocode.rb +11 -9
- data/lib/git_fit/geo.rb +8 -6
- data/lib/git_fit/import/apple_health.rb +84 -82
- data/lib/git_fit/import/local_file.rb +45 -42
- data/lib/git_fit/import.rb +4 -2
- data/lib/git_fit/install/actions.rb +15 -13
- data/lib/git_fit/parser/base.rb +12 -10
- data/lib/git_fit/parser/fit.rb +11 -9
- data/lib/git_fit/parser/gpx.rb +17 -15
- data/lib/git_fit/parser/tcx.rb +22 -20
- data/lib/git_fit/parser.rb +6 -4
- data/lib/git_fit/privacy/polyline_filter.rb +6 -4
- data/lib/git_fit/source/base.rb +25 -23
- data/lib/git_fit/source/tally.rb +9 -7
- data/lib/git_fit/source.rb +4 -2
- data/lib/git_fit/sport_mapper.rb +23 -21
- data/lib/git_fit/sync/base.rb +29 -27
- data/lib/git_fit/sync/garmin.rb +5 -3
- data/lib/git_fit/sync/garmin_base.rb +145 -143
- data/lib/git_fit/sync/garmin_base_di.rb +42 -40
- data/lib/git_fit/sync/garmin_cn.rb +8 -6
- data/lib/git_fit/sync/igpsport.rb +43 -41
- data/lib/git_fit/sync/keep.rb +98 -96
- data/lib/git_fit/sync/lorem.rb +31 -29
- data/lib/git_fit/sync/runner.rb +14 -11
- data/lib/git_fit/sync/strava.rb +61 -52
- data/lib/git_fit/sync/xingzhe.rb +52 -50
- data/lib/git_fit/sync/xoss.rb +68 -66
- data/lib/git_fit/timezone/resolver.rb +5 -3
- data/lib/git_fit/util/tally.rb +10 -8
- data/lib/git_fit/util.rb +2 -0
- data/lib/git_fit/version.rb +3 -1
- metadata +1 -1
data/lib/git_fit/parser/base.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
data/lib/git_fit/parser/fit.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
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(
|
|
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:
|
|
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[
|
|
48
|
-
lon = rec[
|
|
49
|
+
lat = rec['positionLat']
|
|
50
|
+
lon = rec['positionLong']
|
|
49
51
|
return nil unless lat && lon
|
|
50
|
-
ts = rec[
|
|
52
|
+
ts = rec['timestamp']
|
|
51
53
|
time = ts.is_a?(Numeric) ? FIT_EPOCH + ts : parse_time(ts)
|
|
52
|
-
[lat, lon, rec[
|
|
54
|
+
[lat, lon, rec['altitude'], time, rec['heartRate']]
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
def average_heartrate(points)
|
data/lib/git_fit/parser/gpx.rb
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
21
|
-
segments = trk.xpath(
|
|
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(
|
|
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:
|
|
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(
|
|
49
|
-
lat = pt[
|
|
50
|
-
lon = pt[
|
|
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(
|
|
53
|
-
time = parse_time(pt.at_xpath(
|
|
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(
|
|
62
|
+
ext = pt.at_xpath('extensions')
|
|
61
63
|
return nil unless ext
|
|
62
|
-
hr = ext.at_xpath(
|
|
64
|
+
hr = ext.at_xpath('.//hr')
|
|
63
65
|
hr&.text&.to_f
|
|
64
66
|
end
|
|
65
67
|
|
data/lib/git_fit/parser/tcx.rb
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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:
|
|
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(
|
|
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[
|
|
20
|
-
laps = act.xpath(
|
|
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(
|
|
34
|
-
name: act.at_xpath(
|
|
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:
|
|
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(
|
|
55
|
-
total_time = lap.at_xpath(
|
|
56
|
-
avg_hr_node = lap.at_xpath(
|
|
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(
|
|
68
|
+
track = lap.at_xpath('tcx:Track', NS)
|
|
67
69
|
return [] unless track
|
|
68
|
-
track.xpath(
|
|
69
|
-
pos = tp.at_xpath(
|
|
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(
|
|
72
|
-
lon = pos.at_xpath(
|
|
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(
|
|
75
|
-
time = parse_time(tp.at_xpath(
|
|
76
|
-
hr = tp.at_xpath(
|
|
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
|
data/lib/git_fit/parser.rb
CHANGED
|
@@ -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
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
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[
|
|
6
|
-
@ignore_polyline = decode_polyline(config[
|
|
7
|
-
@ignore_range = (config[
|
|
8
|
-
@before_saving = config[
|
|
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?
|
data/lib/git_fit/source/base.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
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[
|
|
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(
|
|
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[
|
|
58
|
+
type = attrs['sport_category'] || attrs[:sport_category]
|
|
57
59
|
parts << type if type
|
|
58
|
-
dist = attrs[
|
|
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[
|
|
63
|
-
name = attrs[
|
|
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 +
|
|
69
|
+
display_name = chars.first(30).join + '…' if chars.size > 30
|
|
68
70
|
parts << display_name
|
|
69
|
-
parts.join(
|
|
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(
|
|
81
|
-
File.join(
|
|
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(
|
|
94
|
-
File.join(
|
|
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 =
|
|
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(
|
|
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[
|
|
136
|
+
attrs['summary_polyline'] = @privacy.filter(attrs['summary_polyline'])
|
|
135
137
|
end
|
|
136
138
|
|
|
137
|
-
existing = @db[:activities].where(run_id: attrs[
|
|
139
|
+
existing = @db[:activities].where(run_id: attrs['run_id']).first
|
|
138
140
|
if existing
|
|
139
|
-
attrs.delete(
|
|
140
|
-
attrs[
|
|
141
|
-
@db[:activities].where(run_id: 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[
|
|
144
|
-
attrs[
|
|
145
|
+
attrs['created_at'] ||= now
|
|
146
|
+
attrs['updated_at'] ||= now
|
|
145
147
|
@db[:activities].insert(attrs)
|
|
146
148
|
end
|
|
147
149
|
end
|
data/lib/git_fit/source/tally.rb
CHANGED
|
@@ -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] +
|
|
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
|
data/lib/git_fit/source.rb
CHANGED
|
@@ -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
|
|
7
|
-
require_relative
|
|
8
|
+
require_relative 'util/tally'
|
|
9
|
+
require_relative 'source/base'
|
|
8
10
|
|
|
9
11
|
GitFit::Source::Tally = GitFit::Util::Tally
|
data/lib/git_fit/sport_mapper.rb
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module GitFit
|
|
2
4
|
module SportMapper
|
|
3
5
|
ALIASES = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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,
|
|
20
|
-
[/\b(?:ride|cycl|bike|mountain\.?bike|gravel|ebike)\w*\b/i,
|
|
21
|
-
[/\b(?:hike|climb|mountaineer|trek)\w*\b/i,
|
|
22
|
-
[/\b(?:walk|stroll)\w*\b/i,
|
|
23
|
-
[/\b(?:swim|pool|laps?)\w*\b/i,
|
|
24
|
-
[/\b(?:ski|snowboard|nordic)\w*\b/i,
|
|
25
|
-
[/\b(?:workout|train|gym|fitness|yoga|pilates|crossfit|elliptical|strength)\w*\b/i,
|
|
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
|
|
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
|
-
|
|
47
|
+
'other'
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def self.lev_match(str)
|