git-fit 0.3.2 → 0.4.1
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 +3 -0
- data/lib/git_fit/cli/export.rb +25 -0
- data/lib/git_fit/cli.rb +3 -0
- data/lib/git_fit/export/defaults.rb +29 -0
- data/lib/git_fit/export/json.rb +112 -0
- data/lib/git_fit/export.rb +7 -0
- data/lib/git_fit/source/base.rb +155 -0
- data/lib/git_fit/source/tally.rb +55 -0
- data/lib/git_fit/source.rb +7 -0
- data/lib/git_fit/version.rb +1 -1
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3c36d045a4ed37a0eaad4f219367f1d514af31c26bcae72e76cbeec7bb5dbb0
|
|
4
|
+
data.tar.gz: ab845ce910206dae48a131315427bf06604bd02988b097eb462ae5805727ab97
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9822b621a741ce52c68c6510f6f73a1da52ae544c4371d05ffe430cacd1d882a914f4bd6959b98add49a13595b0ffc703b4258fd5dd5a6535ad887117d5e8be0
|
|
7
|
+
data.tar.gz: 139da861fd411de27553e13e03ec0b3c7d94e5019c64e3e6ba820415e49f87f43ed58490565c408801554f30c3b0a2fa03133e48c4d783ffa7276b54e9f67e22
|
data/lib/git-fit.rb
CHANGED
|
@@ -51,11 +51,14 @@ require_relative "git_fit/sport_mapper"
|
|
|
51
51
|
require_relative "git_fit/geo"
|
|
52
52
|
require_relative "git_fit/fit"
|
|
53
53
|
require_relative "git_fit/parser"
|
|
54
|
+
require_relative "git_fit/source"
|
|
54
55
|
require_relative "git_fit/timezone/resolver"
|
|
55
56
|
require_relative "git_fit/privacy/polyline_filter"
|
|
56
57
|
require_relative "git_fit/sync/base"
|
|
57
58
|
require_relative "git_fit/sync/strava"
|
|
58
59
|
require_relative "git_fit/db/connection"
|
|
59
60
|
require_relative "git_fit/db/activity"
|
|
61
|
+
require_relative "git_fit/export"
|
|
60
62
|
require_relative "git_fit/db/cli"
|
|
63
|
+
require_relative "git_fit/cli/export"
|
|
61
64
|
require_relative "git_fit/cli"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
|
|
3
|
+
module GitFit
|
|
4
|
+
class ExportCLI < Thor
|
|
5
|
+
desc "json", "Export activities to JSON"
|
|
6
|
+
option :output, type: :string, aliases: "-o", desc: "Output path"
|
|
7
|
+
option :activity, type: :array, desc: "Filter by sport type", aliases: "--act"
|
|
8
|
+
no_commands do
|
|
9
|
+
def git_fit_config
|
|
10
|
+
@git_fit_config ||= GitFit::Config.new(options[:config])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def json
|
|
15
|
+
config = git_fit_config
|
|
16
|
+
db = GitFit::DB::Connection.new(config.db_path).db
|
|
17
|
+
path = options[:output] || config.export_config.dig("json", "path") || "site/activities.json"
|
|
18
|
+
filter = options[:activity]&.map(&:strip)&.map(&:downcase)
|
|
19
|
+
count = Export::JSON.new(db: db, output: path, activity_filter: filter).call
|
|
20
|
+
say_status :done, "Exported #{count} activities to #{path}", :green
|
|
21
|
+
rescue => e
|
|
22
|
+
say_status :error, "JSON export failed: #{e.message}", :red
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/git_fit/cli.rb
CHANGED
|
@@ -13,6 +13,9 @@ module GitFit
|
|
|
13
13
|
desc "db SUBCOMMAND", "Database operations"
|
|
14
14
|
subcommand "db", DB::CLI
|
|
15
15
|
|
|
16
|
+
desc "export SUBCOMMAND", "Export activities to various formats"
|
|
17
|
+
subcommand "export", ExportCLI
|
|
18
|
+
|
|
16
19
|
desc "version", "Show version"
|
|
17
20
|
def version
|
|
18
21
|
puts "git-fit v#{GitFit::VERSION}"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module GitFit
|
|
2
|
+
module Export
|
|
3
|
+
module Defaults
|
|
4
|
+
SVG = {
|
|
5
|
+
background: "#222222", text: "#FFFFFF", track: "#4DD2FF",
|
|
6
|
+
track2: "#4DD2FF", special: "#FFFF00", special2: "#FFFF00",
|
|
7
|
+
empty_cell: "#444444", dim_past: "#555555", font: "Arial",
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
DASHBOARD = {
|
|
11
|
+
background: "#1a1a2e", surface: "#16213e", hover: "#0f3460",
|
|
12
|
+
active_row: "#1a3a6a", border: "#2a2a4a", text: "#e0e0e0",
|
|
13
|
+
text_secondary: "#888", text_dim: "#666", text_insight: "#aaa",
|
|
14
|
+
accent: "#4DD2FF", font: "-apple-system, BlinkMacSystemFont, sans-serif",
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
CATEGORIES = {
|
|
18
|
+
"run" => "#4DD2FF", "walk" => "#4ECDC4",
|
|
19
|
+
"ride" => "#FF6B6B", "hike" => "#FFD93D",
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
MAP = {
|
|
23
|
+
tiles_dark: "https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",
|
|
24
|
+
tiles_light: "https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png",
|
|
25
|
+
echarts_theme: "dark",
|
|
26
|
+
}.freeze
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module GitFit
|
|
4
|
+
module Export
|
|
5
|
+
class JSON
|
|
6
|
+
PLACEHOLDER_POLYLINE = "gqqrFurkeU??".freeze
|
|
7
|
+
|
|
8
|
+
def initialize(db:, output:, activity_filter: nil)
|
|
9
|
+
@db = db
|
|
10
|
+
@output = output
|
|
11
|
+
@activity_filter = activity_filter
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
dataset = @db[:activities].order(Sequel.desc(:start_date))
|
|
16
|
+
dataset = dataset.where(sport_category: @activity_filter) if @activity_filter
|
|
17
|
+
|
|
18
|
+
activities = dataset.all
|
|
19
|
+
|
|
20
|
+
run_id_to_group = {}
|
|
21
|
+
@db[:dedup_group_members].each { |gm| run_id_to_group[gm[:run_id]] = gm[:group_id] }
|
|
22
|
+
|
|
23
|
+
group_members_cache = {}
|
|
24
|
+
|
|
25
|
+
formatted = activities.map do |a|
|
|
26
|
+
gid = run_id_to_group[a[:run_id]]
|
|
27
|
+
if gid
|
|
28
|
+
group_members_cache[gid] ||= @db[:dedup_group_members].where(group_id: gid).all
|
|
29
|
+
composite_activity(a, gid, group_members_cache[gid])
|
|
30
|
+
else
|
|
31
|
+
format_activity(a)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
File.write(@output, ::JSON.pretty_generate(formatted))
|
|
36
|
+
formatted.size
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def format_activity(a)
|
|
42
|
+
base = {
|
|
43
|
+
run_id: a[:run_id],
|
|
44
|
+
name: a[:name],
|
|
45
|
+
distance: a[:distance],
|
|
46
|
+
moving_time: a[:moving_time],
|
|
47
|
+
type: a[:sport_category],
|
|
48
|
+
sport_type: a[:sport_type],
|
|
49
|
+
start_date: a[:start_date],
|
|
50
|
+
start_date_local: a[:start_date_local],
|
|
51
|
+
location_country: a[:location_country],
|
|
52
|
+
summary_polyline: a[:source] == "keep" && a[:summary_polyline] == PLACEHOLDER_POLYLINE ?
|
|
53
|
+
nil : a[:summary_polyline],
|
|
54
|
+
average_heartrate: a[:average_heartrate] && a[:average_heartrate] != 0 ? a[:average_heartrate] : nil,
|
|
55
|
+
max_heartrate: a[:max_heartrate],
|
|
56
|
+
average_cadence: a[:average_cadence],
|
|
57
|
+
max_cadence: a[:max_cadence],
|
|
58
|
+
average_power: a[:average_power],
|
|
59
|
+
max_power: a[:max_power],
|
|
60
|
+
calories: a[:calories],
|
|
61
|
+
average_temperature: a[:average_temperature],
|
|
62
|
+
average_speed: a[:average_speed],
|
|
63
|
+
elevation_gain: a[:elevation_gain],
|
|
64
|
+
source: a[:source],
|
|
65
|
+
}
|
|
66
|
+
if a[:divisions]
|
|
67
|
+
base[:divisions] = ::JSON.parse(a[:divisions])
|
|
68
|
+
end
|
|
69
|
+
base
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def composite_activity(activity, group_id, members)
|
|
73
|
+
run_ids = members.map { |m| m[:run_id] }
|
|
74
|
+
group_activities = @db[:activities].where(run_id: run_ids).all
|
|
75
|
+
|
|
76
|
+
composite = if defined?(GitFit::Dedup::FieldElector)
|
|
77
|
+
elector = GitFit::Dedup::FieldElector.new
|
|
78
|
+
result = {}
|
|
79
|
+
GitFit::Dedup::FieldElector::FIELD_ELECTION_RULES.each_key do |field|
|
|
80
|
+
values = group_activities.map { |a| a[field] }
|
|
81
|
+
sources = group_activities.map { |a| a[:source]&.to_sym }
|
|
82
|
+
result[field] = elector.elect(field, values, sources)
|
|
83
|
+
end
|
|
84
|
+
result
|
|
85
|
+
else
|
|
86
|
+
members.first&.slice(:run_id, :distance, :moving_time, :elapsed_time,
|
|
87
|
+
:average_heartrate, :max_heartrate, :elevation_gain,
|
|
88
|
+
:average_speed, :average_cadence, :max_cadence) || {}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
composite[:run_id] = activity[:run_id]
|
|
92
|
+
composite[:source] = activity[:source]
|
|
93
|
+
composite[:sport_type] = activity[:sport_type]
|
|
94
|
+
composite[:start_date_local] = activity[:start_date_local]
|
|
95
|
+
composite[:location_country] = activity[:location_country]
|
|
96
|
+
composite[:sport_category] = activity[:sport_category]
|
|
97
|
+
|
|
98
|
+
if composite[:distance].to_f > 0 && composite[:moving_time].to_f > 0
|
|
99
|
+
composite[:average_speed] = composite[:distance] / composite[:moving_time]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
composite[:type] = composite[:sport_category]
|
|
103
|
+
composite[:dedup_group] = group_id
|
|
104
|
+
composite[:member_sources] = members.map do |m|
|
|
105
|
+
{ source: m[:source], run_id: m[:run_id], flag: m[:flag], confidence: m[:confidence] }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
composite
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
|
|
3
|
+
module GitFit
|
|
4
|
+
module Source
|
|
5
|
+
class Base
|
|
6
|
+
def initialize(config:, db:, activity_filter: nil, privacy: nil, time_budget: nil)
|
|
7
|
+
@config = config
|
|
8
|
+
@db = db
|
|
9
|
+
@activity_filter = activity_filter
|
|
10
|
+
@privacy = privacy
|
|
11
|
+
@time_budget = time_budget
|
|
12
|
+
@start_time = nil
|
|
13
|
+
@progress_reported = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :progress_reported
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
return 0 unless before_call
|
|
20
|
+
return 0 unless respond_to?(:authenticate) ? authenticate : true
|
|
21
|
+
ids = pending_ids
|
|
22
|
+
return 0 if ids.nil? || ids.empty?
|
|
23
|
+
total = ids.size
|
|
24
|
+
count = 0
|
|
25
|
+
ids.filter_map do |p|
|
|
26
|
+
r = process_one(p)
|
|
27
|
+
count += 1
|
|
28
|
+
report_progress(count, total, build_progress_info(r)) if r
|
|
29
|
+
r
|
|
30
|
+
end.size
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def before_call
|
|
34
|
+
@start_time = Time.now
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def pending_ids
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def process_one(pending)
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def build_progress_info(attrs)
|
|
47
|
+
parts = []
|
|
48
|
+
sd = attrs["start_date"] || attrs[:start_date]
|
|
49
|
+
if sd
|
|
50
|
+
d = Date.parse(sd.to_s) rescue nil
|
|
51
|
+
if d
|
|
52
|
+
date_str = d.year == Date.today.year ? d.strftime("%m-%d") : d.strftime("%Y-%m-%d")
|
|
53
|
+
parts << date_str
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
type = attrs["sport_category"] || attrs[:sport_category]
|
|
57
|
+
parts << type if type
|
|
58
|
+
dist = attrs["distance"] || attrs[:distance]
|
|
59
|
+
if dist && dist.to_f > 0
|
|
60
|
+
parts << "#{format('%.1f', dist.to_f / 1000)}km"
|
|
61
|
+
end
|
|
62
|
+
source = attrs["source"] || attrs[:source] || source_label
|
|
63
|
+
name = attrs["name"] || attrs[:name]
|
|
64
|
+
display_name = name.to_s.strip
|
|
65
|
+
display_name = "#{source} Activity" if display_name.empty?
|
|
66
|
+
chars = display_name.chars
|
|
67
|
+
display_name = chars.first(30).join + "…" if chars.size > 30
|
|
68
|
+
parts << display_name
|
|
69
|
+
parts.join(" | ") unless parts.empty?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
protected
|
|
73
|
+
|
|
74
|
+
def time_expired?
|
|
75
|
+
return false unless @time_budget.is_a?(Numeric) && @time_budget > 0 && @start_time
|
|
76
|
+
(Time.now - @start_time) > @time_budget
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def raw_dir
|
|
80
|
+
src = self.class.name.split("::").last.downcase
|
|
81
|
+
File.join("data", "raw", src)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def raw_path(platform_id)
|
|
85
|
+
raise NotImplementedError
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def raw_file_exists?(platform_id)
|
|
89
|
+
File.exist?(raw_path(platform_id))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def std_dir
|
|
93
|
+
src = self.class.name.split("::").last.downcase
|
|
94
|
+
File.join("data", "std", src)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def std_path(platform_id, ext = "json")
|
|
98
|
+
File.join(std_dir, "#{platform_id}.#{ext}")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def report_progress(count, total = nil, info = nil)
|
|
102
|
+
return unless show_progress?(count, total)
|
|
103
|
+
@progress_reported = true
|
|
104
|
+
line = "#{source_label}: #{count}"
|
|
105
|
+
line += "/#{total}" if total
|
|
106
|
+
line += " #{info}" if info
|
|
107
|
+
line += " "
|
|
108
|
+
print "\r#{line}"
|
|
109
|
+
$stdout.flush
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def show_progress?(count, total)
|
|
113
|
+
return true if count == 1
|
|
114
|
+
return true if total && (total - count) <= 50
|
|
115
|
+
step = progress_step(total)
|
|
116
|
+
step <= 1 || count % step == 0
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def progress_step(total)
|
|
120
|
+
return 1 unless total && total > 0
|
|
121
|
+
[1, (1.5 * (total ** 0.4)).to_i].max
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def source_label
|
|
125
|
+
name = self.class.name
|
|
126
|
+
name ? name.split("::").last : "Source"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def upsert_activity(attrs)
|
|
130
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
131
|
+
now = Time.now.utc
|
|
132
|
+
|
|
133
|
+
if @privacy && !@privacy.before_saving?
|
|
134
|
+
attrs["summary_polyline"] = @privacy.filter(attrs["summary_polyline"])
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
existing = @db[:activities].where(run_id: attrs["run_id"]).first
|
|
138
|
+
if existing
|
|
139
|
+
attrs.delete("created_at")
|
|
140
|
+
attrs["updated_at"] = now
|
|
141
|
+
@db[:activities].where(run_id: attrs["run_id"]).update(attrs)
|
|
142
|
+
else
|
|
143
|
+
attrs["created_at"] ||= now
|
|
144
|
+
attrs["updated_at"] ||= now
|
|
145
|
+
@db[:activities].insert(attrs)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def skip_type?(sport_type)
|
|
150
|
+
return false unless @activity_filter
|
|
151
|
+
!@activity_filter.include?(sport_type.to_s.downcase)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module GitFit
|
|
2
|
+
module Source
|
|
3
|
+
class Tally
|
|
4
|
+
DIGITS = %w[零 一 二 三 四 五 六 七 八 九 十]
|
|
5
|
+
|
|
6
|
+
def initialize(count = 0)
|
|
7
|
+
@count = count
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add(n = 1)
|
|
11
|
+
@count += n
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
return @count.to_s if @count > 9999
|
|
16
|
+
chinese_numeral
|
|
17
|
+
rescue StandardError
|
|
18
|
+
@count.to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def chinese_numeral
|
|
24
|
+
n = @count
|
|
25
|
+
return DIGITS[0] if n == 0
|
|
26
|
+
return DIGITS[n] if n <= 10
|
|
27
|
+
|
|
28
|
+
result = ""
|
|
29
|
+
thousands = n / 1000
|
|
30
|
+
n %= 1000
|
|
31
|
+
result += DIGITS[thousands] + "千" if thousands > 0
|
|
32
|
+
|
|
33
|
+
hundreds = n / 100
|
|
34
|
+
n %= 100
|
|
35
|
+
if hundreds > 0
|
|
36
|
+
result += DIGITS[hundreds] + "百"
|
|
37
|
+
elsif thousands > 0 && n > 0
|
|
38
|
+
result += "零"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
tens = n / 10
|
|
42
|
+
ones = n % 10
|
|
43
|
+
if tens > 0
|
|
44
|
+
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 += "零"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
result += DIGITS[ones] if ones > 0
|
|
51
|
+
result
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/git_fit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: git-fit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lax
|
|
@@ -105,11 +105,15 @@ files:
|
|
|
105
105
|
- exe/git-fit
|
|
106
106
|
- lib/git-fit.rb
|
|
107
107
|
- lib/git_fit/cli.rb
|
|
108
|
+
- lib/git_fit/cli/export.rb
|
|
108
109
|
- lib/git_fit/config.rb
|
|
109
110
|
- lib/git_fit/config_template.rb
|
|
110
111
|
- lib/git_fit/db/activity.rb
|
|
111
112
|
- lib/git_fit/db/cli.rb
|
|
112
113
|
- lib/git_fit/db/connection.rb
|
|
114
|
+
- lib/git_fit/export.rb
|
|
115
|
+
- lib/git_fit/export/defaults.rb
|
|
116
|
+
- lib/git_fit/export/json.rb
|
|
113
117
|
- lib/git_fit/fit.rb
|
|
114
118
|
- lib/git_fit/fit/decoder.rb
|
|
115
119
|
- lib/git_fit/geo.rb
|
|
@@ -121,6 +125,9 @@ files:
|
|
|
121
125
|
- lib/git_fit/parser/gpx.rb
|
|
122
126
|
- lib/git_fit/parser/tcx.rb
|
|
123
127
|
- lib/git_fit/privacy/polyline_filter.rb
|
|
128
|
+
- lib/git_fit/source.rb
|
|
129
|
+
- lib/git_fit/source/base.rb
|
|
130
|
+
- lib/git_fit/source/tally.rb
|
|
124
131
|
- lib/git_fit/sport_mapper.rb
|
|
125
132
|
- lib/git_fit/sync/base.rb
|
|
126
133
|
- lib/git_fit/sync/strava.rb
|