forked from veerendra2/fitbit-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.py
More file actions
199 lines (174 loc) · 5.31 KB
/
cli.py
File metadata and controls
199 lines (174 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# -*- coding: utf-8 -*-
"""
CLI Arguments Parser
"""
import argparse
import re
from datetime import datetime, timedelta
from . import __version__
def _get_date_range(delta_days):
return (
(datetime.today() - timedelta(days=delta_days)).strftime("%Y-%m-%d"),
datetime.today().strftime("%Y-%m-%d"),
)
def _parse_relative_dates(date_str):
"""Helper function to parse relative date patterns"""
if date_str.lower() == "yesterday":
return ((datetime.today() - timedelta(days=1)).strftime("%Y-%m-%d"), None)
match = re.match(r"^last-(\d+)-(days|weeks|months)$", date_str, re.IGNORECASE)
if match:
number = int(match.group(1))
unit = match.group(2).lower()
multipliers = {"days": 1, "weeks": 7, "months": 30}
return _get_date_range(number * multipliers[unit])
match = re.match(r"^last-(week|month)$", date_str, re.IGNORECASE)
if match:
unit = match.group(1).lower()
days = 7 if unit == "week" else 30
return _get_date_range(days)
return None
def parse_date_range(date_str):
"""Date parser that handles both absolute and relative dates"""
relative_result = _parse_relative_dates(date_str)
if relative_result:
return relative_result
# Handle absolute dates
dates = date_str.split(",")
start_date = datetime.strptime(dates[0], "%Y-%m-%d").date()
try:
end_date = datetime.strptime(dates[1], "%Y-%m-%d").date()
if start_date > end_date:
raise ValueError("Start date must not be after end date")
except IndexError:
end_date = None
return (start_date, end_date)
def parse_arguments():
"""Argument parser"""
parser = argparse.ArgumentParser(
description="Fitbit CLI -- Access your Fitbit data at your terminal.",
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument(
"-i",
"--init-auth",
action="store_true",
help="Initialize Fitbit iterative authentication setup",
)
parser.add_argument(
"-j",
"--json",
action="store_true",
help="Output table data as JSON.",
)
parser.add_argument(
"-r",
"--raw-json",
action="store_true",
help="Output raw JSON from the Fitbit API.",
)
group = parser.add_argument_group(
"APIs",
"Specify a date, date range (YYYY-MM-DD[,YYYY-MM-DD]), or relative date.\n"
"Relative dates: yesterday, last-week, last-month, last-N-days/weeks/months (e.g., last-2-days).\n"
"If not provided, defaults to today's date.",
)
group.add_argument(
"-s",
"--sleep",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show Sleep Log by Date Range.",
)
group.add_argument(
"-o",
"--spo2",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show SpO2 Summary by Interval.",
)
group.add_argument(
"-e",
"--heart",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show Heart Rate Time Series by Date Range.",
)
group.add_argument(
"-a",
"--active-zone",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show AZM Time Series by Interval.",
)
group.add_argument(
"-b",
"--breathing-rate",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show Breathing Rate Summary by Interval.",
)
group.add_argument(
"-H",
"--hrv",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show HRV Summary by Interval.",
)
group.add_argument(
"-B",
"--body",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show Body Time Series for Weight, BMI, and Body Fat.",
)
group.add_argument(
"-t",
"--activities",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show Daily Activity Summary.",
)
group.add_argument(
"-u",
"--user-profile",
action="store_true",
help="Show Profile.",
)
group.add_argument(
"-d",
"--devices",
action="store_true",
help="Show Devices.",
)
parser.add_argument(
"-v",
"--version",
action="version",
version=f"%(prog)s v{__version__}",
help="Show fitbit-cli version",
)
args = parser.parse_args()
data_args = {
k: v
for k, v in vars(args).items()
if k not in ("json", "raw_json", "init_auth", "version")
}
if not args.init_auth and not any(data_args.values()):
parser.error("No arguments provided. At least one argument is required.")
return args