Full swing off of python onto rust
This commit is contained in:
parent
64d5dbfea8
commit
2c25aabc6a
6 changed files with 17 additions and 204 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,2 +1 @@
|
||||||
venv/
|
/target
|
||||||
__pycache__/
|
|
||||||
|
|
|
||||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "day-reporter"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
184
dump.py
184
dump.py
|
|
@ -1,184 +0,0 @@
|
||||||
import things
|
|
||||||
import argparse
|
|
||||||
import re
|
|
||||||
import random
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
with open('./emojis.txt', 'r') as emoji_file:
|
|
||||||
EMOJIS = [l.strip() for l in emoji_file.readlines()]
|
|
||||||
|
|
||||||
def mine_tags(tags, place):
|
|
||||||
if place is None or not 'tags' in place:
|
|
||||||
return tags
|
|
||||||
|
|
||||||
tags.extend(place['tags'])
|
|
||||||
|
|
||||||
def has_tag(task, tag):
|
|
||||||
area = things.areas(uuid=task['area']) if 'area' in task else None
|
|
||||||
project = things.projects(uuid=task['project']) if 'project' in task else None
|
|
||||||
heading_project = things.projects(uuid=things.get(uuid=task['heading'])['project']) if 'heading' in task else None
|
|
||||||
project_area = things.areas(uuid=project['area']) if project is not None and 'area' in project else None
|
|
||||||
tags = []
|
|
||||||
mine_tags(tags, task)
|
|
||||||
mine_tags(tags, area)
|
|
||||||
mine_tags(tags, heading_project)
|
|
||||||
mine_tags(tags, project)
|
|
||||||
mine_tags(tags, project_area)
|
|
||||||
return tag in tags
|
|
||||||
|
|
||||||
def tasks_to_heirarchy(top_level_comment, tasks):
|
|
||||||
heir = {}
|
|
||||||
for task in tasks:
|
|
||||||
task_leaf = (task, {})
|
|
||||||
if 'project' not in task and 'heading' not in task:
|
|
||||||
heir[task['uuid']] = task_leaf
|
|
||||||
elif 'heading' in task:
|
|
||||||
h_uuid = task['heading']
|
|
||||||
head = things.get(h_uuid)
|
|
||||||
|
|
||||||
p_uuid = head['project'] # This must exist
|
|
||||||
proj = things.get(p_uuid)
|
|
||||||
proj_title, proj_sublevel = heir[p_uuid] if p_uuid in heir else (proj, {})
|
|
||||||
head_title, head_sublevel = proj_sublevel[h_uuid] if h_uuid in proj_sublevel else (head, {})
|
|
||||||
head_sublevel[task['uuid']] = task_leaf
|
|
||||||
proj_sublevel[head['uuid']] = (head_title, head_sublevel)
|
|
||||||
heir[p_uuid] = (proj_title, proj_sublevel)
|
|
||||||
else: # The project must not be None here
|
|
||||||
p_uuid = task['project']
|
|
||||||
proj = things.get(p_uuid)
|
|
||||||
proj_title, proj_sublevel = heir[p_uuid] if p_uuid in heir else (proj, {})
|
|
||||||
proj_sublevel[task['uuid']] = task_leaf
|
|
||||||
heir[p_uuid] = (proj_title, proj_sublevel)
|
|
||||||
|
|
||||||
return (top_level_comment, heir)
|
|
||||||
|
|
||||||
def parse_note(note):
|
|
||||||
note_block_pattern = r"```report\n([\s\S]*?)\n?```"
|
|
||||||
return ' '.join(re.findall(note_block_pattern, note))
|
|
||||||
|
|
||||||
def make_recursive_formatter(formatter):
|
|
||||||
def recursive_format(structure, depth):
|
|
||||||
(node, branches) = structure
|
|
||||||
notes = parse_note(node['notes'])
|
|
||||||
node_str = formatter.node(node, notes)
|
|
||||||
if len(branches) == 1:
|
|
||||||
item = list(branches.values())[0]
|
|
||||||
node_str += formatter.single(recursive_format(item, depth))
|
|
||||||
elif len(branches) > 1:
|
|
||||||
new_depth = depth + 2
|
|
||||||
bullets = [formatter.bullet(f"{recursive_format(item, new_depth)}", new_depth) for item in branches.values()]
|
|
||||||
node_str += ''.join(map(lambda b: f'\n{b}', bullets))
|
|
||||||
|
|
||||||
return node_str
|
|
||||||
return recursive_format
|
|
||||||
|
|
||||||
class TodayFormatter:
|
|
||||||
def node(self, node, notes):
|
|
||||||
return f"{node['title']}{'' if notes == '' else f' {notes}'}"
|
|
||||||
|
|
||||||
def single(self, subtext):
|
|
||||||
return f" > {subtext}"
|
|
||||||
|
|
||||||
def bullet(self, subtext, depth):
|
|
||||||
space = ''.join([' '] * depth)
|
|
||||||
return f"{space}- {subtext}"
|
|
||||||
|
|
||||||
class LogbookFormatter:
|
|
||||||
def node(self, node, notes):
|
|
||||||
node_text = f"{node['title']}{'' if notes == '' or node['status'] == 'canceled' else f' {notes}'}"
|
|
||||||
if node['status'] == 'canceled':
|
|
||||||
node_text = f"~{node_text}~"
|
|
||||||
|
|
||||||
return node_text
|
|
||||||
|
|
||||||
def single(self, subtext):
|
|
||||||
return f" > {subtext}"
|
|
||||||
|
|
||||||
def bullet(self, subtext, depth):
|
|
||||||
space = ''.join([' '] * depth)
|
|
||||||
return f"{space}- {subtext}"
|
|
||||||
|
|
||||||
# Note: the characters on the RHS are all "lookalikes". The do not == the left side
|
|
||||||
VOWEL_MAP = {
|
|
||||||
'a': 'а',
|
|
||||||
'e': 'e',
|
|
||||||
'i': 'і',
|
|
||||||
'o': 'о',
|
|
||||||
'u': 'ս',
|
|
||||||
}
|
|
||||||
|
|
||||||
def sanitize_name(name):
|
|
||||||
for normal_ch, special_ch in VOWEL_MAP.items():
|
|
||||||
name = name.replace(normal_ch, special_ch)
|
|
||||||
|
|
||||||
return name
|
|
||||||
|
|
||||||
def sanitize_string(s, mapping):
|
|
||||||
for original_str, new_str in mapping.items():
|
|
||||||
s = s.replace(original_str, new_str)
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def sanitize_mentions(task):
|
|
||||||
if 'tags' not in task:
|
|
||||||
return
|
|
||||||
|
|
||||||
people_tags = { t[1:]: sanitize_name(t[1:]) for t in task['tags'] if t[0] == '@' }
|
|
||||||
# For now just sanitize title and notes (headings might also make sense?)
|
|
||||||
task['title'] = sanitize_string(task['title'], people_tags)
|
|
||||||
task['notes'] = sanitize_string(task['notes'], people_tags)
|
|
||||||
|
|
||||||
def same_date(d1, d2):
|
|
||||||
day1 = str(d1).split()[0]
|
|
||||||
day2 = str(d2).split()[0]
|
|
||||||
return day1 == day2
|
|
||||||
|
|
||||||
def generate_signoff_message(target_tag):
|
|
||||||
format_tasks = make_recursive_formatter(LogbookFormatter())
|
|
||||||
import datetime
|
|
||||||
today = datetime.datetime.today()
|
|
||||||
reportable = list(filter(lambda t: has_tag(t, target_tag) and same_date(today, t['stop_date']) and t['status'] == 'completed', things.logbook()))
|
|
||||||
|
|
||||||
for task in reportable:
|
|
||||||
sanitize_mentions(task)
|
|
||||||
|
|
||||||
structured_tasks = tasks_to_heirarchy({ 'title': 'Stopping now', 'notes': '', 'status': '' }, sorted(reportable, key=by_modified_timestamp))
|
|
||||||
return format_tasks(structured_tasks, 0)
|
|
||||||
|
|
||||||
def by_modified_timestamp(val):
|
|
||||||
return datetime.fromisoformat(val['modified'])
|
|
||||||
|
|
||||||
def generate_today_message(target_tag):
|
|
||||||
format_tasks = make_recursive_formatter(TodayFormatter())
|
|
||||||
reportable = list(filter(lambda t: has_tag(t, target_tag), things.today()))
|
|
||||||
|
|
||||||
for task in reportable:
|
|
||||||
sanitize_mentions(task)
|
|
||||||
|
|
||||||
top_level_comment = ' '.join(map(lambda e: f':{e}:', random.choices(EMOJIS, k=3)))
|
|
||||||
structured_tasks = tasks_to_heirarchy({ 'title': top_level_comment, 'notes': '' }, sorted(reportable, key=by_modified_timestamp))
|
|
||||||
return format_tasks(structured_tasks, 0)
|
|
||||||
|
|
||||||
def generate_track_message(target_tag):
|
|
||||||
t = datetime.today() - timedelta(days=14)
|
|
||||||
projects = filter(lambda x: x['type'] == 'project', things.logbook())
|
|
||||||
recent_projects = filter(lambda x: datetime.fromisoformat(x['modified']) > t, projects)
|
|
||||||
selected_projects = filter(lambda x: 'tags' in x and target_tag in x['tags'], recent_projects)
|
|
||||||
return '\n'.join(map(lambda x: x['title'], selected_projects))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
parser = argparse.ArgumentParser(description="Generate iknow_vacation_remote style messages from Things 3 Tasks")
|
|
||||||
parser.add_argument('tags', metavar='TAG', type=str, nargs="+", help="a word in the overall tag")
|
|
||||||
parser.add_argument('--signoff', action=argparse.BooleanOptionalAction, help="Get tasks from the logbook and generate output for a signoff message")
|
|
||||||
parser.add_argument('--track', action=argparse.BooleanOptionalAction, help="Get tasks from the logbook and generate output for a track meeting")
|
|
||||||
args = parser.parse_args()
|
|
||||||
target_tag = ' '.join(args.tags)
|
|
||||||
|
|
||||||
if args.signoff:
|
|
||||||
print(generate_signoff_message(target_tag))
|
|
||||||
elif args.track:
|
|
||||||
print(generate_track_message(target_tag))
|
|
||||||
else:
|
|
||||||
print(generate_today_message(target_tag))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
things.py
|
|
||||||
22
shell.nix
22
shell.nix
|
|
@ -1,20 +1,8 @@
|
||||||
with import <nixpkgs> {};
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
with pkgs.python3Packages;
|
|
||||||
|
|
||||||
stdenv.mkDerivation {
|
|
||||||
name = "python";
|
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
pip
|
pkgs.rustc
|
||||||
python310
|
pkgs.cargo
|
||||||
virtualenv
|
] ++ [ pkgs.libiconv pkgs.darwin.apple_sdk.frameworks.CoreServices ];
|
||||||
];
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
SOURCE_DATE_EPOCH=$(date +%s) # so that we can use python wheels
|
|
||||||
virtualenv --no-setuptools venv > /dev/null
|
|
||||||
export PATH=$PWD/venv/bin:$PATH > /dev/null
|
|
||||||
export PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
||||||
pip install -r requirements.txt > /dev/null
|
|
||||||
'';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
src/main.rs
Normal file
3
src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
Reference in a new issue