Skip to content

Commit 22183fd

Browse files
authored
feat: added aw-cli with some utils (#110)
1 parent d7cba36 commit 22183fd

File tree

4 files changed

+142
-5
lines changed

4 files changed

+142
-5
lines changed

aw_cli/__main__.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""
2+
The idea behind this `aw` or `aw-cli` wrapper script is to act as a collection of helper tools,
3+
and perhaps even as a way to list and run ActivityWatch modules on a system (a bit like aw-qt, but without the GUI).
4+
"""
5+
6+
from pathlib import Path
7+
from datetime import datetime
8+
import subprocess
9+
10+
import click
11+
12+
from aw_cli.log import find_oldest_log, print_log, LOGLEVELS
13+
14+
15+
@click.group()
16+
@click.option("--testing", is_flag=True)
17+
def main(testing: bool = False):
18+
pass
19+
20+
21+
@main.command()
22+
@click.pass_context
23+
def qt(ctx):
24+
return subprocess.call(
25+
["aw-qt"] + (["--testing"] if ctx.parent.params["testing"] else [])
26+
)
27+
28+
29+
@main.command()
30+
def directories():
31+
# Print all directories
32+
from aw_core.dirs import get_data_dir, get_config_dir, get_cache_dir, get_log_dir
33+
34+
print("Directory paths used")
35+
print(" - config: ", get_config_dir(None))
36+
print(" - data: ", get_data_dir(None))
37+
print(" - logs: ", get_log_dir(None))
38+
print(" - cache: ", get_cache_dir(None))
39+
40+
41+
@main.command()
42+
@click.pass_context
43+
@click.argument("module_name", type=str, required=False)
44+
@click.option(
45+
"--since",
46+
type=click.DateTime(formats=["%Y-%m-%d"]),
47+
help="Only show logs since this date",
48+
)
49+
@click.option(
50+
"--level",
51+
type=click.Choice(LOGLEVELS),
52+
help="Only show logs of this level, or higher.",
53+
)
54+
def logs(ctx, module_name: str = None, since: datetime = None, level: str = None):
55+
from aw_core.dirs import get_log_dir
56+
57+
testing = ctx.parent.params["testing"]
58+
logdir: Path = Path(get_log_dir(None))
59+
60+
# find the oldest logfile in each of the subdirectories in the logging directory, and print the last lines in each one.
61+
62+
if module_name:
63+
print_oldest_log(logdir / module_name, testing, since, level)
64+
else:
65+
for subdir in sorted(logdir.iterdir()):
66+
if subdir.is_dir():
67+
print_oldest_log(subdir, testing, since, level)
68+
69+
70+
def print_oldest_log(path, testing, since, level):
71+
path = find_oldest_log(path, testing)
72+
if path:
73+
print_log(path, since, level)
74+
else:
75+
print(f"No logfile found in {path}")
76+
77+
78+
if __name__ == "__main__":
79+
main()

aw_cli/log.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from pathlib import Path
2+
from datetime import datetime
3+
4+
5+
LOGLEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
6+
7+
8+
def print_log(path: Path, since: datetime = None, level: str = None):
9+
if not path.is_file():
10+
return
11+
12+
show_levels = LOGLEVELS[LOGLEVELS.index(level) :] if level else None
13+
14+
lines_printed = 0
15+
with path.open("r") as f:
16+
lines = f.readlines()
17+
print(f"Logs for module {path.parent.name} ({path.name}, {len(lines)} lines)")
18+
for line in lines:
19+
if since:
20+
try:
21+
linedate = datetime.strptime(line.split(" ")[0], "%Y-%m-%d")
22+
except ValueError:
23+
# Could not parse the date, so skip this line
24+
# NOTE: Just because the date could not be parsed, doesn't mean there isn't meaningful info there.
25+
# Would be better to find the first line after the cutoff, and then just print everything past that.
26+
continue
27+
# Skip lines before the date
28+
if linedate < since:
29+
continue
30+
if level:
31+
if not any(level in line for level in show_levels):
32+
continue
33+
print(line, end="")
34+
lines_printed += 1
35+
36+
print(f" (Filtered {lines_printed}/{len(lines)} lines)")
37+
38+
39+
def find_oldest_log(path: Path, testing=False) -> Path:
40+
if not path.is_dir():
41+
return
42+
43+
logfiles = [
44+
f
45+
for f in path.iterdir()
46+
if f.is_file()
47+
and f.name.endswith(".log")
48+
and ("testing" in f.name if testing else "testing" not in f.name)
49+
]
50+
if not logfiles:
51+
return
52+
53+
logfiles.sort(key=lambda f: f.stat().st_mtime)
54+
logfile = logfiles[-1]
55+
return logfile

aw_core/dirs.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def ensure_path_exists(path: str) -> None:
1414

1515
def _ensure_returned_path_exists(f: GetDirFunc) -> GetDirFunc:
1616
@wraps(f)
17-
def wrapper(subpath: Optional[str]) -> str:
17+
def wrapper(subpath: Optional[str] = None) -> str:
1818
path = f(subpath)
1919
ensure_path_exists(path)
2020
return path
@@ -23,24 +23,24 @@ def wrapper(subpath: Optional[str]) -> str:
2323

2424

2525
@_ensure_returned_path_exists
26-
def get_data_dir(module_name: Optional[str]) -> str:
26+
def get_data_dir(module_name: Optional[str] = None) -> str:
2727
data_dir = appdirs.user_data_dir("activitywatch")
2828
return os.path.join(data_dir, module_name) if module_name else data_dir
2929

3030

3131
@_ensure_returned_path_exists
32-
def get_cache_dir(module_name: Optional[str]) -> str:
32+
def get_cache_dir(module_name: Optional[str] = None) -> str:
3333
cache_dir = appdirs.user_cache_dir("activitywatch")
3434
return os.path.join(cache_dir, module_name) if module_name else cache_dir
3535

3636

3737
@_ensure_returned_path_exists
38-
def get_config_dir(module_name: Optional[str]) -> str:
38+
def get_config_dir(module_name: Optional[str] = None) -> str:
3939
config_dir = appdirs.user_config_dir("activitywatch")
4040
return os.path.join(config_dir, module_name) if module_name else config_dir
4141

4242

4343
@_ensure_returned_path_exists
44-
def get_log_dir(module_name: Optional[str]) -> str: # pragma: no cover
44+
def get_log_dir(module_name: Optional[str] = None) -> str: # pragma: no cover
4545
log_dir = appdirs.user_log_dir("activitywatch")
4646
return os.path.join(log_dir, module_name) if module_name else log_dir

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ packages = [
1515
{ include = "aw_query" },
1616
]
1717

18+
[tool.poetry.scripts]
19+
aw-cli = "aw_cli.__main__:main"
20+
1821
[tool.poetry.dependencies]
1922
python = "^3.7"
2023
jsonschema = "^4.3"

0 commit comments

Comments
 (0)