git-fit 0.0.1 → 0.1.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 +18 -0
- data/lib/git_fit/cli.rb +45 -0
- data/lib/git_fit/config.rb +124 -0
- data/lib/git_fit/config_template.rb +44 -0
- data/lib/git_fit/sync/base.rb +16 -0
- data/lib/git_fit/sync/strava.rb +7 -0
- data/lib/git_fit/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9551667addc842c5ddb8357d32b9d4eb040eedc236eeb3c940f59ea52f8be329
|
|
4
|
+
data.tar.gz: 3ede4c5df18fd6a3380b5aff8845bf2ac1ca7d1074c2507e647e05363bf745fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 437969ed9c1dd8ec94de2449cbe8e71e8201bcfe2299fd3dcdcf44c8ae6b4901482b99fdd3459482395896a10c96c455a1acff8c69fd2e242c81e1befdb88941
|
|
7
|
+
data.tar.gz: 11ffe29a5aecb4c305f361c81ad3eef6f73394fc9abb00f75486d68a32caac3de565ef941236120622b63c61059b20486f790e32290ef016da93141266d20b5e
|
data/lib/git-fit.rb
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
require "thor"
|
|
2
|
+
require "yaml"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module GitFit
|
|
6
|
+
@registered_configs = []
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_reader :registered_configs
|
|
10
|
+
|
|
11
|
+
def register_config_source(prefix:, keys:, config_path:)
|
|
12
|
+
@registered_configs << { prefix: prefix, keys: keys, config_path: config_path }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
2
16
|
|
|
3
17
|
require_relative "git_fit/version"
|
|
18
|
+
require_relative "git_fit/config"
|
|
19
|
+
require_relative "git_fit/config_template"
|
|
20
|
+
require_relative "git_fit/sync/base"
|
|
21
|
+
require_relative "git_fit/sync/strava"
|
|
4
22
|
require_relative "git_fit/cli"
|
data/lib/git_fit/cli.rb
CHANGED
|
@@ -4,6 +4,8 @@ module GitFit
|
|
|
4
4
|
class CLI < Thor
|
|
5
5
|
package_name "git-fit"
|
|
6
6
|
|
|
7
|
+
class_option :config, type: :string, desc: "Config file path", aliases: "-c"
|
|
8
|
+
|
|
7
9
|
def self.exit_on_failure?
|
|
8
10
|
true
|
|
9
11
|
end
|
|
@@ -12,5 +14,48 @@ module GitFit
|
|
|
12
14
|
def version
|
|
13
15
|
puts "git-fit v#{GitFit::VERSION}"
|
|
14
16
|
end
|
|
17
|
+
|
|
18
|
+
desc "init", "Generate config.yml"
|
|
19
|
+
option :output, type: :string, desc: "Output path", aliases: "-o"
|
|
20
|
+
def init
|
|
21
|
+
path = options[:output] || "config/config.yml"
|
|
22
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
23
|
+
|
|
24
|
+
if File.exist?(path)
|
|
25
|
+
existing = YAML.safe_load(File.read(path)) || {}
|
|
26
|
+
merged = deep_merge(ConfigTemplate.hash, existing)
|
|
27
|
+
File.write(path, YAML.dump(merged))
|
|
28
|
+
say_status :merged, path, :green
|
|
29
|
+
else
|
|
30
|
+
File.write(path, ConfigTemplate.generate)
|
|
31
|
+
say_status :created, path, :green
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
no_commands do
|
|
36
|
+
def git_fit_config
|
|
37
|
+
@git_fit_config ||= GitFit::Config.new(options[:config])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def deep_merge(template, existing)
|
|
41
|
+
result = deep_dup(template)
|
|
42
|
+
existing.each do |key, val|
|
|
43
|
+
if val.is_a?(Hash) && result[key].is_a?(Hash)
|
|
44
|
+
result[key] = deep_merge(result[key], val)
|
|
45
|
+
else
|
|
46
|
+
result[key] = val
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
result
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def deep_dup(obj)
|
|
53
|
+
case obj
|
|
54
|
+
when Hash then obj.each_with_object({}) { |(k, v), h| h[k] = deep_dup(v) }
|
|
55
|
+
when Array then obj.map { |v| deep_dup(v) }
|
|
56
|
+
else obj
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
15
60
|
end
|
|
16
61
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module GitFit
|
|
4
|
+
# 三优先级: env var > YAML > DEFAULTS
|
|
5
|
+
class Config
|
|
6
|
+
DEFAULTS = {
|
|
7
|
+
"database" => { "path" => "db/fit.sqlite3" },
|
|
8
|
+
"sync" => {},
|
|
9
|
+
"export" => {
|
|
10
|
+
"json" => { "path" => "site/activities.json" },
|
|
11
|
+
"svg" => { "dir" => "site/svg" },
|
|
12
|
+
"csv" => { "path" => "site/workouts.csv" },
|
|
13
|
+
"gpx" => { "path" => "site/gpx_out" }
|
|
14
|
+
},
|
|
15
|
+
"privacy" => {
|
|
16
|
+
"start_end_range" => 200
|
|
17
|
+
},
|
|
18
|
+
"system" => {
|
|
19
|
+
"units" => "metric",
|
|
20
|
+
"log_level" => "info",
|
|
21
|
+
"timezone" => "Asia/Shanghai"
|
|
22
|
+
}
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
DEFAULT_PATH = "config/config.yml"
|
|
26
|
+
KNOWN_TOP_KEYS = %w[database sync export privacy system].freeze
|
|
27
|
+
|
|
28
|
+
def initialize(path = nil)
|
|
29
|
+
@data = deep_dup(DEFAULTS)
|
|
30
|
+
load_file(path) if path
|
|
31
|
+
load_file(DEFAULT_PATH) if !path && File.exist?(DEFAULT_PATH)
|
|
32
|
+
load_env
|
|
33
|
+
validate!
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def [](key)
|
|
37
|
+
@data[key]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def dig(*keys)
|
|
41
|
+
@data.dig(*keys)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sync_config(source)
|
|
45
|
+
@data.dig("sync", source) || {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def privacy_config
|
|
49
|
+
@data["privacy"] || {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def export_config
|
|
53
|
+
@data["export"] || {}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def db_path
|
|
57
|
+
@data.dig("database", "path") || DEFAULTS["database"]["path"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def load_env
|
|
63
|
+
@data["database"]["path"] = ENV["GIT_FIT_DATABASE_PATH"] if ENV["GIT_FIT_DATABASE_PATH"]
|
|
64
|
+
|
|
65
|
+
GitFit.registered_configs.each do |reg|
|
|
66
|
+
reg[:keys].each do |key|
|
|
67
|
+
env_name = "#{reg[:prefix]}_#{key.to_s.upcase}"
|
|
68
|
+
next unless ENV.key?(env_name)
|
|
69
|
+
set_nested(@data, reg[:config_path] + [key.to_s], ENV[env_name])
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def validate!
|
|
75
|
+
@data.each_key do |k|
|
|
76
|
+
next if k.empty? || KNOWN_TOP_KEYS.include?(k)
|
|
77
|
+
warn "Config: unknown top-level key '#{k}'"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def load_file(path)
|
|
82
|
+
yaml = YAML.safe_load(File.read(path)) || {}
|
|
83
|
+
deep_merge!(@data, yaml)
|
|
84
|
+
rescue => e
|
|
85
|
+
warn "Config: failed to load #{path}: #{e.message}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def set_nested(hash, keys, value)
|
|
89
|
+
last = keys.pop
|
|
90
|
+
keys.each { |k| hash[k] ||= {}; hash = hash[k] }
|
|
91
|
+
hash[last] = typed_value(value)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def typed_value(str)
|
|
95
|
+
case str
|
|
96
|
+
when /\A\d+\z/ then str.to_i
|
|
97
|
+
when /\A\d+\.\d+\z/ then str.to_f
|
|
98
|
+
when /\Atrue\z/i then true
|
|
99
|
+
when /\Afalse\z/i then false
|
|
100
|
+
when /\Anil\z/i then nil
|
|
101
|
+
else str
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def deep_merge!(target, source)
|
|
106
|
+
source.each do |key, val|
|
|
107
|
+
if val.is_a?(Hash) && target[key].is_a?(Hash)
|
|
108
|
+
deep_merge!(target[key], val)
|
|
109
|
+
else
|
|
110
|
+
target[key] = deep_dup(val)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
target
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def deep_dup(obj)
|
|
117
|
+
case obj
|
|
118
|
+
when Hash then obj.each_with_object({}) { |(k, v), h| h[k] = deep_dup(v) }
|
|
119
|
+
when Array then obj.map { |v| deep_dup(v) }
|
|
120
|
+
else obj
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module GitFit
|
|
2
|
+
class ConfigTemplate
|
|
3
|
+
def self.generate
|
|
4
|
+
<<~YAML
|
|
5
|
+
---
|
|
6
|
+
database:
|
|
7
|
+
path: db/fit.sqlite3
|
|
8
|
+
|
|
9
|
+
sync:
|
|
10
|
+
# strava: # env: GIT_FIT_STRAVA_CLIENT_ID
|
|
11
|
+
# client_id: "" # env: GIT_FIT_STRAVA_CLIENT_SECRET
|
|
12
|
+
# refresh_token: "" # env: GIT_FIT_STRAVA_REFRESH_TOKEN
|
|
13
|
+
|
|
14
|
+
# garmin: # env: GIT_FIT_GARMIN_EMAIL
|
|
15
|
+
# email: "" # env: GIT_FIT_GARMIN_PASSWORD
|
|
16
|
+
# password: ""
|
|
17
|
+
|
|
18
|
+
# keep: # env: GIT_FIT_KEEP_PHONE
|
|
19
|
+
# phone: "" # env: GIT_FIT_KEEP_PASSWORD
|
|
20
|
+
# password: ""
|
|
21
|
+
|
|
22
|
+
# igpsport: # env: GIT_FIT_IGPSPORT_PHONE
|
|
23
|
+
# phone: "" # env: GIT_FIT_IGPSPORT_PASSWORD
|
|
24
|
+
# password: ""
|
|
25
|
+
|
|
26
|
+
export:
|
|
27
|
+
json:
|
|
28
|
+
path: site/activities.json
|
|
29
|
+
|
|
30
|
+
privacy:
|
|
31
|
+
start_end_range: 200
|
|
32
|
+
|
|
33
|
+
system:
|
|
34
|
+
units: metric
|
|
35
|
+
log_level: info
|
|
36
|
+
timezone: Asia/Shanghai
|
|
37
|
+
YAML
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.hash
|
|
41
|
+
YAML.safe_load(generate) || {}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module GitFit
|
|
2
|
+
module Sync
|
|
3
|
+
class Base
|
|
4
|
+
def self.register_config(*keys)
|
|
5
|
+
return if keys.empty?
|
|
6
|
+
|
|
7
|
+
source = name.split("::").last.downcase
|
|
8
|
+
GitFit.register_config_source(
|
|
9
|
+
prefix: "GIT_FIT_#{source.upcase}",
|
|
10
|
+
keys: keys,
|
|
11
|
+
config_path: ["sync", source]
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
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.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lax
|
|
@@ -35,6 +35,10 @@ files:
|
|
|
35
35
|
- exe/git-fit
|
|
36
36
|
- lib/git-fit.rb
|
|
37
37
|
- lib/git_fit/cli.rb
|
|
38
|
+
- lib/git_fit/config.rb
|
|
39
|
+
- lib/git_fit/config_template.rb
|
|
40
|
+
- lib/git_fit/sync/base.rb
|
|
41
|
+
- lib/git_fit/sync/strava.rb
|
|
38
42
|
- lib/git_fit/version.rb
|
|
39
43
|
homepage: https://github.com/Lax/git-fit
|
|
40
44
|
licenses:
|