Full swing off of python onto rust

This commit is contained in:
Campbell Alden 2023-07-20 13:35:52 +09:00
parent 64d5dbfea8
commit 2c25aabc6a
6 changed files with 17 additions and 204 deletions

3
.gitignore vendored
View file

@ -1,2 +1 @@
venv/
__pycache__/
/target

8
Cargo.toml Normal file
View 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
View file

@ -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))

View file

@ -1 +0,0 @@
things.py

View file

@ -1,20 +1,8 @@
with import <nixpkgs> {};
with pkgs.python3Packages;
stdenv.mkDerivation {
name = "python";
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pip
python310
virtualenv
];
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
'';
pkgs.rustc
pkgs.cargo
] ++ [ pkgs.libiconv pkgs.darwin.apple_sdk.frameworks.CoreServices ];
}

3
src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}