· 8 years ago · May 09, 2018, 08:24 AM
1#!/usr/bin/env node
2const fs = require('fs');
3const util = require('util');
4const moment = require('moment');
5const sqlite = require('sqlite');
6const path = require('path');
7
8const IS_START_MAGIC = str => {
9 str = str.toLowerCase();
10 return str.startsWith('#start') || str.startsWith('#begin');
11};
12
13const IS_END_MAGIC = str => {
14 str = str.toLowerCase();
15 return (
16 str.startsWith('#stop') ||
17 str.startsWith('#done') ||
18 str.startsWith('#pause') ||
19 str.startsWith('#end')
20 );
21};
22
23const readJson = async path => {
24 const fileContents = await util.promisify(fs.readFile)(path);
25 return JSON.parse(fileContents);
26};
27
28const writeJson = async (obj, path) => {
29 const jsonString = JSON.stringify(obj);
30 return util.promisify(fs.writeFile)(path, jsonString);
31};
32
33const assertMsg = (cond, msg) => {
34 if (!cond) {
35 console.log();
36 console.log(msg);
37 process.exit(1);
38 }
39};
40
41const main = async () => {
42 const INPUT_PATH = process.argv[2];
43 const DB_PATH = process.argv[3];
44
45 assertMsg(
46 INPUT_PATH && DB_PATH,
47 `Error: You need both input and database paths.
48 Input: ${INPUT_PATH || '<none>'}
49 Database: ${DB_PATH || '<none>'}`
50 );
51
52 const db = await sqlite.open(DB_PATH);
53 const trello = await readJson(INPUT_PATH);
54
55 assertMsg(trello.actions != null, "Trello JSON must have 'actions' property");
56
57 await db.exec(`
58 CREATE TABLE IF NOT EXISTS tasks (
59 id TEXT PRIMARY KEY,
60 title TEXT,
61 tag TEXT,
62 duration INTEGER,
63 updated_at DAATETIME,
64 url TEXT,
65 spare_tags TEXT
66 )
67 `);
68
69 // First pass: parse actions into {id: [{time, isStart, isEnd}...] ...} format.
70 const markers = {};
71 for (let i = trello.actions.length - 1; i > 0; i--) {
72 const action = trello.actions[i];
73 if (action.type === 'commentCard') {
74 const comment = action.data.text;
75 const id = action.data.card.id;
76 const isStart = IS_START_MAGIC(comment);
77 const isEnd = IS_END_MAGIC(comment);
78 if (isStart || isEnd) {
79 markers[id] = markers[id] || []; // ensure exists
80
81 // Determine timestamp from msg.
82 const tokens = comment
83 .split('\n')[0]
84 .split(' ')
85 .slice(1)
86 .map(e => e.trim())
87 .filter(e => e.length > 0);
88
89 let timestamp = null;
90 if (tokens.length > 0) {
91 // Parse timestamp from comment
92 let timeStr = tokens[0].toLowerCase();
93 timestamp = moment(timeStr, 'h:ma');
94 } else {
95 // Get timestamp from when comment was added
96 timestamp = moment(action.date);
97 }
98
99 // Strip day info off timestamp, only leave time.
100 timestamp.dayOfYear(0);
101
102 markers[id].push({timestamp, isStart, isEnd});
103 }
104 }
105 }
106
107 // Second pass: Parse the markers into time spent per card.
108 const timePerCard = {};
109 for (let k of Object.keys(markers)) {
110 const events = markers[k];
111 let mins = 0;
112 let lastStart = null;
113
114 for (let e of events) {
115 if (e.isStart) {
116 lastStart = e.timestamp;
117 } else if (e.isEnd && lastStart !== null) {
118 while (e.timestamp.isBefore(lastStart)) {
119 e.timestamp.add(1, 'days');
120 }
121 const diff = e.timestamp.diff(lastStart, 'minutes');
122 mins += diff;
123 lastStart = null;
124 }
125 }
126
127 timePerCard[k] = {mins, latest: lastStart};
128 }
129
130 // Third pass: Join with card info
131 const info = {};
132 for (let k of Object.keys(timePerCard)) {
133 const mins = timePerCard[k].mins;
134 const card = trello.cards.filter(e => e.id === k)[0];
135 const labels = card.labels.map(e => e.name);
136 const splitName = card.name.split(':');
137 info[k] = {
138 name: card.name,
139 url: card.shortUrl,
140 date: card.dateLastActivity,
141 labels,
142 primaryLabel: labels[0] || (splitName.length > 1 ? splitName[0] : ''),
143 mins
144 };
145 }
146
147 // Save this all to the db
148 const taskStmt = await db.prepare(
149 `REPLACE INTO tasks(id, title, tag, duration, updated_at, url, spare_tags) VALUES(?,?,?,?,?,?,?);`
150 );
151 for (k of Object.keys(info)) {
152 r = info[k];
153 let data = [k, r.name, r.primaryLabel, r.mins, r.date, r.url, r.labels.join(',')];
154 await taskStmt.run(data);
155 console.log(`k:${k} name:${r.name}`);
156 }
157};
158
159main().catch(e => {
160 console.log();
161 console.log('Error: ');
162 console.log(e);
163 process.exit(1);
164});