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,16 +1,19 @@
1
- require "json"
2
- require "fileutils"
3
- require_relative "reverse_geocode"
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+ require_relative 'reverse_geocode'
4
6
 
5
7
  module GitFit
6
8
  module Geo
7
9
  class DivisionDetector
8
- KEEP_PLACEHOLDER = "gqqrFurkeU??".freeze
10
+ KEEP_PLACEHOLDER = 'gqqrFurkeU??'
9
11
  SAMPLE_INTERVAL_DEG = 0.01
10
- CACHE_DIR = "data/cache"
11
- POINT_CACHE_FILE = File.join(CACHE_DIR, "geo-point.jsonl")
12
+ CACHE_DIR = 'data/cache'
13
+ POINT_CACHE_FILE = File.join(CACHE_DIR, 'geo-point.jsonl')
12
14
 
13
- def initialize(db:, amap_key: nil, batch: 20, limit: nil, strategy: "proportional", time_budget: nil, dry_run: false)
15
+ def initialize(db:, amap_key: nil, batch: 20, limit: nil, strategy: 'proportional', time_budget: nil,
16
+ dry_run: false)
14
17
  @db = db
15
18
  @geocoder = ReverseGeocode.new(amap_key: amap_key)
16
19
  @amap_key = amap_key
@@ -39,9 +42,9 @@ module GitFit
39
42
  @total = activities.size
40
43
 
41
44
  if @total > 0 && @amap_key.nil?
42
- $stdout.puts "WARNING: AMAP_API_KEY not configured. China coordinates will fail."
43
- $stdout.puts "Set via: ENV[\"AMAP_API_KEY\"] or config.yml sync.amap.api_key"
44
- $stdout.puts ""
45
+ $stdout.puts 'WARNING: AMAP_API_KEY not configured. China coordinates will fail.'
46
+ $stdout.puts 'Set via: ENV["AMAP_API_KEY"] or config.yml sync.amap.api_key'
47
+ $stdout.puts ''
45
48
  end
46
49
 
47
50
  $stdout.puts "Geo detection: #{@total} activities to process"
@@ -57,9 +60,9 @@ module GitFit
57
60
 
58
61
  def pending_activities
59
62
  dataset = @db[:activities]
60
- .where(Sequel.lit("summary_polyline IS NOT NULL"))
63
+ .where(Sequel.lit('summary_polyline IS NOT NULL'))
61
64
  .where(Sequel.lit("summary_polyline != ''"))
62
- .where(Sequel.lit("divisions IS NULL"))
65
+ .where(Sequel.lit('divisions IS NULL'))
63
66
  .order(Sequel.desc(:start_date))
64
67
  dataset = dataset.limit(@limit) if @limit
65
68
  dataset.all
@@ -121,13 +124,15 @@ module GitFit
121
124
  update_db(run_id, summary) unless @dry_run
122
125
  $stdout.puts " #{prefix} #{run_id} → #{format_divisions(divisions)} (#{samples.size} samples)"
123
126
  @processed += 1
124
- rescue => e
127
+ rescue StandardError => e
125
128
  $stdout.puts " #{prefix} #{run_id} → error: #{e.message}"
126
129
  @errors += 1
127
130
  end
128
131
 
129
132
  def format_divisions(divisions)
130
- divisions.map { |d| [d[:province], d[:city], d[:district], d[:township]].compact.reject(&:empty?).join(" ") }.join(" / ")
133
+ divisions.map do |d|
134
+ [d[:province], d[:city], d[:district], d[:township]].compact.reject(&:empty?).join(' ')
135
+ end.join(' / ')
131
136
  end
132
137
 
133
138
  def decode_points(polyline)
@@ -165,21 +170,21 @@ module GitFit
165
170
  key_counts[key] += 1
166
171
  end
167
172
  key_counts.map do |key, count|
168
- province, city, district, township = key.split("|")
173
+ province, city, district, township = key.split('|')
169
174
  { province: province, city: city, district: district, township: township, count: count }
170
175
  end.sort_by { |r| -r[:count] }
171
176
  end
172
177
 
173
178
  def normalize_entry(raw)
174
179
  return nil unless raw
175
- province = raw[:province] || raw["p"] || raw["province"]
176
- province = nil if province.is_a?(Array) || province.to_s.empty? || province == "中华人民共和国"
180
+ province = raw[:province] || raw['p'] || raw['province']
181
+ province = nil if province.is_a?(Array) || province.to_s.empty? || province == '中华人民共和国'
177
182
  return nil unless province
178
183
  {
179
184
  province: province,
180
- city: pick_field(raw, :city, "city", "c", "city") { |v| valid_name?(v) },
181
- district: pick_field(raw, :district, "dist", "d"),
182
- township: pick_field(raw, :township, "town", "t"),
185
+ city: pick_field(raw, :city, 'city', 'c', 'city') { |v| valid_name?(v) },
186
+ district: pick_field(raw, :district, 'dist', 'd'),
187
+ township: pick_field(raw, :township, 'town', 't'),
183
188
  }
184
189
  end
185
190
 
@@ -196,7 +201,7 @@ module GitFit
196
201
  end
197
202
 
198
203
  def division_key(result)
199
- [result[:province], result[:city], result[:district], result[:township]].join("|")
204
+ [result[:province], result[:city], result[:district], result[:township]].join('|')
200
205
  end
201
206
 
202
207
  def build_summary(divisions, total_points)
@@ -210,15 +215,15 @@ module GitFit
210
215
  h[:n] = r[:count]
211
216
  h
212
217
  end
213
- { divisions: division_list, total_sampled: total_points, source: "amap", processed_at: Time.now.utc.iso8601 }
218
+ { divisions: division_list, total_sampled: total_points, source: 'amap', processed_at: Time.now.utc.iso8601 }
214
219
  end
215
220
 
216
221
  def lookup_point_cache(coord_key)
217
222
  return nil unless File.exist?(POINT_CACHE_FILE)
218
- File.open(POINT_CACHE_FILE, "r") do |f|
223
+ File.open(POINT_CACHE_FILE, 'r') do |f|
219
224
  f.each_line do |line|
220
225
  entry = JSON.parse(line) rescue next
221
- return entry if entry["ck"] == coord_key
226
+ return entry if entry['ck'] == coord_key
222
227
  end
223
228
  end
224
229
  nil
@@ -229,7 +234,7 @@ module GitFit
229
234
  entry[:city] = result[:city] if result[:city]
230
235
  entry[:dist] = result[:district] if result[:district]
231
236
  entry[:town] = result[:township] if result[:township]
232
- File.open(POINT_CACHE_FILE, "a") { |f| f.puts(JSON.generate(entry)) }
237
+ File.open(POINT_CACHE_FILE, 'a') { |f| f.puts(JSON.generate(entry)) }
233
238
  end
234
239
 
235
240
  def update_db(run_id, summary)
@@ -237,7 +242,7 @@ module GitFit
237
242
  end
238
243
 
239
244
  def mark_empty_divisions(run_id)
240
- @db[:activities].where(run_id: run_id).update(divisions: "[]")
245
+ @db[:activities].where(run_id: run_id).update(divisions: '[]')
241
246
  end
242
247
 
243
248
  def load_caches
@@ -1,12 +1,14 @@
1
- require "net/http"
2
- require "json"
3
- require "uri"
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
4
6
 
5
7
  module GitFit
6
8
  module Geo
7
9
  class NominatimClient
8
- BASE_URL = "https://nominatim.openstreetmap.org/reverse".freeze
9
- USER_AGENT = "git-fit/1.0 (geo-detector)"
10
+ BASE_URL = 'https://nominatim.openstreetmap.org/reverse'
11
+ USER_AGENT = 'git-fit/1.0 (geo-detector)'
10
12
 
11
13
  def initialize
12
14
  @mutex = Mutex.new
@@ -20,17 +22,17 @@ module GitFit
20
22
  uri.query = URI.encode_www_form(
21
23
  lat: lat,
22
24
  lon: lng,
23
- format: "jsonv2",
25
+ format: 'jsonv2',
24
26
  addressdetails: 1,
25
27
  zoom: 10,
26
28
  )
27
29
  resp = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
28
30
  req = Net::HTTP::Get.new(uri)
29
- req["User-Agent"] = USER_AGENT
31
+ req['User-Agent'] = USER_AGENT
30
32
  http.request(req)
31
33
  end
32
34
  body = JSON.parse(resp.body)
33
- if body["error"]
35
+ if body['error']
34
36
  raise "Nominatim error: #{body["error"]}"
35
37
  end
36
38
  parse_response(body)
@@ -39,10 +41,10 @@ module GitFit
39
41
  private
40
42
 
41
43
  def parse_response(body)
42
- addr = body["address"] || {}
43
- country = addr["country"]
44
- state = addr["state"]
45
- { province: country, city: state, district: nil, township: nil, source: "nominatim" }
44
+ addr = body['address'] || {}
45
+ country = addr['country']
46
+ state = addr['state']
47
+ { province: country, city: state, district: nil, township: nil, source: 'nominatim' }
46
48
  end
47
49
 
48
50
  def rate_limit
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GitFit
2
4
  module Geo
3
5
  module Polyline
@@ -43,7 +45,7 @@ module GitFit
43
45
  end
44
46
 
45
47
  def encode(points)
46
- result = ""
48
+ result = ''
47
49
  plat = 0
48
50
  plng = 0
49
51
 
@@ -63,7 +65,7 @@ module GitFit
63
65
 
64
66
  def encode_number(num)
65
67
  num = num < 0 ? ~(num << 1) : (num << 1)
66
- result = ""
68
+ result = ''
67
69
 
68
70
  while num >= 0x20
69
71
  result += ((0x20 | (num & 0x1f)) + 63).chr
@@ -1,13 +1,15 @@
1
- require_relative "coord_transform"
2
- require_relative "amap_client"
3
- require_relative "nominatim_client"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'coord_transform'
4
+ require_relative 'amap_client'
5
+ require_relative 'nominatim_client'
4
6
 
5
7
  module GitFit
6
8
  module Geo
7
9
  class ReverseGeocode
8
10
  CHINA_BBOX = {
9
11
  min_lng: 73, max_lng: 135,
10
- min_lat: 18, max_lat: 54,
12
+ min_lat: 18, max_lat: 54
11
13
  }.freeze
12
14
 
13
15
  def initialize(amap_key: nil)
@@ -31,17 +33,17 @@ module GitFit
31
33
  end
32
34
 
33
35
  def lookup_china(lat, lng)
34
- raise "AMap API key not configured" unless @amap
36
+ raise 'AMap API key not configured' unless @amap
35
37
  gcj_lat, gcj_lng = CoordTransform.wgs84_to_gcj02(lat, lng)
36
38
  @amap.reverse_geocode(gcj_lat, gcj_lng)
37
- rescue => e
38
- { province: nil, city: nil, district: nil, township: nil, source: "error", error: e.message }
39
+ rescue StandardError => e
40
+ { province: nil, city: nil, district: nil, township: nil, source: 'error', error: e.message }
39
41
  end
40
42
 
41
43
  def lookup_intl(lat, lng)
42
44
  @nominatim.reverse_geocode(lat, lng)
43
- rescue => e
44
- { province: nil, city: nil, district: nil, township: nil, source: "error", error: e.message }
45
+ rescue StandardError => e
46
+ { province: nil, city: nil, district: nil, township: nil, source: 'error', error: e.message }
45
47
  end
46
48
  end
47
49
  end
data/lib/git_fit/geo.rb CHANGED
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GitFit
2
4
  module Geo
3
5
  end
4
6
  end
5
7
 
6
- require_relative "geo/polyline"
7
- require_relative "geo/coord_transform"
8
- require_relative "geo/amap_client"
9
- require_relative "geo/nominatim_client"
10
- require_relative "geo/reverse_geocode"
11
- require_relative "geo/division_detector"
8
+ require_relative 'geo/polyline'
9
+ require_relative 'geo/coord_transform'
10
+ require_relative 'geo/amap_client'
11
+ require_relative 'geo/nominatim_client'
12
+ require_relative 'geo/reverse_geocode'
13
+ require_relative 'geo/division_detector'