以下に、これまでの実装内容をまとめてテキスト化したものを記載します。
export interface Todo {
text: string; // タスクの本文
date: string; // 作成日 (YYYY-MM-DD)
id: string; // タスクID (id:xxx 形式から抽出)
completed: boolean; // 完了状態
projects: string[]; // プロジェクト (+で始まる要素の配列)
contexts: string[]; // コンテキスト (@で始まる要素の配列)
}
{
interface TodoPart {
completed: boolean;
date: string;
id: string;
text: string;
projects: string[];
contexts: string[];
}
function processRest(rest: string): { id: string, text: string, projects: string[], contexts: string[] } {
const idMatch = rest.match(/(?:^|\s)id:([^\s]+)/);
const id = idMatch?.[1] || '';
const projects = [...rest.matchAll(/\+(\S+)/g)].map(m => m[1]);
const contexts = [...rest.matchAll(/@(\S+)/g)].map(m => m[1]);
const text = rest
.replace(/(?:^|\s)id:[^\s]+/g, '')
.replace(/[+@]\S+/g, '')
.replace(/\s+/g, ' ')
.trim();
return { id, text, projects, contexts };
}
}
TodoLine
= _? line:(
CompletedPart
/ UncompletedPart
) _? {
return {
completed: line.completed,
date: line.date,
id: line.id,
text: line.text,
projects: line.projects,
contexts: line.contexts
};
}
CompletedPart
= "x" __ completionDate:Date? __ createdDate:Date? __ rest:RestOfLine {
return {
completed: true,
date: createdDate || "",
...processRest(rest)
};
}
UncompletedPart
= priority:Priority? __ createdDate:Date? __ rest:RestOfLine {
const priorityText = priority ? `(${priority}) ` : '';
return {
completed: false,
date: createdDate || "",
...processRest(priorityText + rest)
};
}
Priority
= "(" $[A-Z] ")" { return text().substring(1, 2); }
Date
= date:$[0-9]{4}-[0-9]{2}-[0-9]{2} { return date; }
RestOfLine
= rest:.* { return rest.join('').trim(); }
__ = [ \t]+
_ = [ \t]*
import { parse } from './todo-grammar'; // tspegで生成されたパーサー
export function parseTodoLine(line: string): Todo {
try {
const parsed = parse(line);
return {
completed: parsed.completed,
date: parsed.date,
id: parsed.id,
text: parsed.text,
projects: parsed.projects,
contexts: parsed.contexts
};
} catch (e) {
return {
completed: false,
date: '',
id: '',
text: line.trim(),
projects: [],
contexts: []
};
}
}
const line1 = "x 2023-10-01 2022-09-15 Finish project +work +urgent @office id:PROJ-123";
const todo1 = parseTodoLine(line1);
/* 結果:
{
completed: true,
date: "2022-09-15",
id: "PROJ-123",
text: "Finish project",
projects: ["work", "urgent"],
contexts: ["office"]
}
*/
const line2 = "(B) 2023-01-01 Start new year resolution +personal @home id:NY2023";
const todo2 = parseTodoLine(line2);
/* 結果:
{
completed: false,
date: "2023-01-01",
id: "NY2023",
text: "(B) Start new year resolution",
projects: ["personal"],
contexts: ["home"]
}
*/
-
複数のプロジェクトとコンテキストを抽出
+project
や@context
を配列として保持- 例:
+work +urgent
→projects: ["work", "urgent"]
-
完了タスクの処理
x
で始まるタスクを完了済みと判定- 完了日と作成日を区別(作成日を
date
に格納)
-
優先度処理
(A)
形式の優先度を検出しテキストに保持
-
ID抽出
id:xxx
形式のIDを抽出して専用フィールドに格納
-
エラーハンドリング
- パース失敗時は入力をそのままテキストに格納
const line3 = "2023-12-25 +shopping @gift プレゼントを買う id:XMAS2023";
const todo3 = parseTodoLine(line3);
/* 結果:
{
completed: false,
date: "2023-12-25",
id: "XMAS2023",
text: "プレゼントを買う",
projects: ["shopping"],
contexts: ["gift"]
}
*/
const line4 = "x 2023-11-11 2023-10-10 +work @meeting 会議の議事録を書く id:MEET-456";
const todo4 = parseTodoLine(line4);
/* 結果:
{
completed: true,
date: "2023-10-10",
id: "MEET-456",
text: "会議の議事録を書く",
projects: ["work"],
contexts: ["meeting"]
}
*/
この実装により、todo.txtの仕様に従って1行をパースし、プロジェクトやコンテキストを含むすべての情報を適切に抽出できます。必要に応じて、さらに拡張や調整が可能です! 🚀
1. 型定義の修正(
priority
フィールド追加)2. PEG文法ファイルの修正
3. パーサー関数の修正
4. テストケース例
主な修正点
優先度の分離処理
(A)
形式をテキストから除去してpriority
フィールドに格納priority: "A"
,text: "タスク"
優先度のないケースの対応
テキストのクリーンアップ
型定義の整合性
Todo
インターフェースにpriority
フィールドを追加これで優先度が正しく処理されるようになりました! 🎉