takker-scheduler-2/task
takker-scheduler-2で使うタスクの解析と書き込みを行うModule
takker-scheduler/taskからコメント解析機能を抜いた
2021-03-14
20:44:19 libaryを追加&更新
sleep
scrapbox-insert-text-2
03:59:21 12時制になっていたのを24時制に修正した
2021-01-21
02:44:14 parseが直接テキストを解析できるようにした
textに文字列を渡す
01:37:58 isTaskを追加した
機能
parse({lineNo})
lineNo行にあるtritask task lineを解析する
tritask task lineでなければundefinedを返す
nextTask(taskData)
繰り返し属性 (takker-scheduler)を読んで次回のタスクを作成する
属性がなければundefiendを返す
taskDataにタスクのobjectを渡す
write(taskData, {overwrite})
tritask task lineを書き込む
書き込む行はtaskDataで指定されている
taskDataにタスクのobjectを渡す
overwriteにfalseを渡すと指定された行の次行に書き込む
defaultはtrue
指定された行にタスク以外の非空白文字列が書き込まれているときは、optionの有無にかかわらず次行に書き込まれる
/icons/hr.icon
codes
dependencies
code:script.js
import {
goHead, goLine,
} from '../scrapbox-edit-emulation/script.js';
import {press} from '../scrapbox-keyboard-emulation-2/script.js';
import {insertText} from '../scrapbox-insert-text-2/script.js';
import {
format, parse, set, addDays, isAfter,
intervalToDuration, differenceInMinutes,
} from '../date-fns.min.js/script.js';
import {sleep} from '../sleep/script.js';
設定とかもろもろ
code:script.js
// タスクの書式
const taskReg = /^(\d{4}-\d{2}-\d{2}) ( {5}|\d{2}:\d{2}) ( {4}|\d{4}) ( {8}|\d{2}:\d{2}:\d{2}) ( {8}|\d{2}:\d{2}:\d{2})(^\n*)$/;
code:test1.js
import {
format, parse, set, addDays, isAfter,
intervalToDuration, differenceInMinutes,
} from '../date-fns.min.js/script.js';
console.log(parse('13:45', 'HH:mm', new Date()));
taskを解析する
lineNo行にあるtritask task lineがあるかどうか探す
なかったら何もしない
code:script.js
function parseDate({lineNo, text}) {
// lineNo行がタスク行でなければ何もしない
text = text ?? scrapbox.Page.lineslineNo.text;
if (!isTask(text)) return undefined;
// タスクが書き込まれた行を解析する
const baseDateString, plan, estimate, start, end, content = text.match(taskReg)?.slice(1);
const baseDate = parse(baseDateString, 'yyyy-MM-dd', new Date());
// 実績時刻を解析する
// 開始時刻より終了時刻の方が前だったら、日付を越えているとみなす
const rStart = !/^\s*$/.test(start) ? parse(start, 'HH:mm:ss', baseDate) : undefined;
let rEnd = !/^\s*$/.test(end) ? parse(end, 'HH:mm:ss', baseDate) : undefined;
if (rStart && rEnd && isAfter(rStart, rEnd)) rEnd = addDays(rEnd, 1);
return {
type: 'task',
date: baseDate,
plan: {
start: !/^\s*$/.test(plan) ?
parse(plan, 'HH:mm', {hours: planH, minutes: planM}) :
undefined,
duration: !/^\s*$/.test(estimate) ?
{minutes: estimate} :
undefined,
},
record: {start: rStart, end: rEnd,},
lineNo,
...parseAttributes({content}),
};
}
export {parseDate as parse};
helper関数
属性を解析する
タスク名→属性
属性を抜いたタスク名も返す
code:script.js
function parseAttributes({content}) {
const repeat = content.split(/\s/)
.find(fragment => /^rep:\d+$/.test(fragment))
?.replace(/^rep:(\d+)$/, '$1');
const rawContent = content.split(/\s/).map(text =>
text.replace(/^skip:月火水木金土日平休+|rep:\d+$/,''))
.filter(text => text.trim() !== '').join(' ');
return {
content: rawContent,
repeat: repeat !== undefined ? parseInt(repeat) : undefined,
skip: content.split(/\s/)
.find(fragment => /^skip:月火水木金土日平休+$/u.test(fragment))
?.replace(/^skip:(月火水木金土日平休+)$/u, '$1')?.match(/./ug)
//Date.prototype.getDay()の書式に変換する
?.flatMap(dayString => {
switch(dayString) {
case '日':
return 0;
case '月':
return 1;
case '火':
return 2;
case '水':
return 3;
case '木':
return 4;
case '金':
return 5;
case '土':
return 6;
case '休':
return 0,6;
case '平':
return 1,2,3,4,5;
}
}) ?? [],
};
}
属性→タスク名
code:script.js
function fromAttributes({content, skip, repeat}) {
let skipStr = '';
if (skip && skip.length > 0) {
let skipStrList = skip.every(dayNumber => 1,2,3,4,5.includes(dayNumber)) ?
['平', ...skip.filter(dayNumber => !1,2,3,4,5.includes(dayNumber))] : skip;
skipStrList = skipStrList.every(dayNumber => 0, 6.includes(dayNumber)) ?
['休', ...skipStrList.filter(dayNumber => !0, 6.includes(dayNumber))] : skipStrList;
skipStrList = skipStrList.map(dayNumber => {
switch(dayNumber) {
case 0:
return '日';
case 1:
return '月';
case 2:
return '火';
case 3:
return '水';
case 4:
return '木';
case 5:
return '金';
case 6:
return '土';
case '平':
case '休':
return dayNumber;
}
});
skipStr = skipStrList.length !== 0
? skip:${skipStrList.join('')}
: '';
} else {
skipStr = '';
}
return [(content ?? ''),
(skipStr !== '' ? ${skipStr} : ''),
(repeat !== undefined ? rep:${repeat} : ''),].join('');
}
タスクの判定
解析までしなくていいときはこっちを使う
code:script.js
export function isTask(text) {
return taskReg.test(text);
}
繰り返し属性を解析して、次のタスクを作る
code:script.js
export function nextTask({type, date, plan, record, skip, repeat, content, lineNo}) {
if (type !== 'task') throw Error(${type} is an invalid type.);
if (repeat === undefined) return undefined;
//console.log({date, skip, repeat});
let nextDate = addDays(date, repeat);
// skip属性があったら、指定された曜日以外になるまで日付をずらす
if (skip.length !== 0) {
// 全ての曜日がスキップに設定されていた場合は空文字を返す
if (0,1,2,3,4,5,6.every(day => skip.includes(day))) return '';
while (skip.includes(nextDate.getDay())) {
nextDate = addDays(nextDate + 1);
}
}
// 見積もり時間を設定する
const duration = plan?.duration
// 実績から見積もり時間を計算する
?? {minutes: differenceInMinutes(record.end, record.start)};
return {
type: 'task',
date: nextDate,
plan: {start: plan.start, duration: duration},
content, skip, repeat,
lineNo,
};
}
タスクを書き込む
overwriteがtrueのとき
tritask task lineがあったら上書きする
tritask task lineでなければ↓と同じ
overwriteがfalseのとき
挿入位置の次行に書き込む
defaultはtrue
code:script.js
export async function write(
{type, date, plan, record, content, lineNo, skip, repeat} = {},
{overwrite = true} = {}) {
//console.log('Write: %o', {type, date, plan, record, content, lineNo, skip, repeat});
const text = create({type, date, plan, record, content, skip, repeat});
//console.log(texts);
const presentTask = parseDate({lineNo});
//console.log({presentTask});
goLine({index: lineNo});
await sleep(10);
// 上書きしないときとタスク以外の非空白文字列があるときは次行に書き込む
if (!overwrite || (!presentTask && !/^\s*$/.test(scrapbox.Page.lineslineNo.text))) {
press('Enter');
}
selectLine();
await insertText(text);
}
taskの文字列を作成する
code:script.js
export function create({type, date, plan, record, content, skip, repeat}) {
switch(type) {
case 'task':
return [
'`',
format(date, 'yyyy-MM-dd'),
' ',
plan?.start ? format(plan.start, 'HH:mm') : ' '.repeat(5),
' ',
plan?.duration ?
String(plan.duration.minutes).padStart(4, '0') :
' '.repeat(4),
' ',
record?.start ? format(record.start, 'HH:mm:ss') : ' '.repeat(8),
' ',
record?.end ? format(record.end, 'HH:mm:ss') : ' '.repeat(8),
'`',
fromAttributes({content, skip, repeat}),
].join('');
default:
throw Error(${type} is an invalid type.);
}
}
Utilities
code:script.js
// 全選択
function selectLine() {
goHead();
press('End', {shiftKey: true});
}
#2021-03-14 03:59:16
#2021-03-13 14:50:58
#2021-01-21 01:37:53
#2021-01-18 11:12:32
#2021-01-16 15:43:59