以下に、これまでの実装内容をまとめてテキスト化したものを記載します。
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文法ファイルの修正
{ interface TodoPart { completed: boolean; date: string; id: string; text: string; priority: string; // 追加 projects: string[]; contexts: string[]; } function processRest( rest: string, priority?: string // 優先度を引数で受け取る ): { id: string, text: string, priority: string, projects: string[], contexts: string[] } { const idMatch = rest.match(/(?:^|\s)id:([^\s]+)/); const id = idMatch?.[1] || ''; // 優先度記号をテキストから削除 const cleanedRest = priority ? rest.replace(`(${priority}) `, '') : rest; // プロジェクト/コンテキスト抽出 const projects = [...cleanedRest.matchAll(/\+(\S+)/g)].map(m => m[1]); const contexts = [...cleanedRest.matchAll(/@(\S+)/g)].map(m => m[1]); // 最終テキストのクリーニング const text = cleanedRest .replace(/(?:^|\s)id:[^\s]+/g, '') .replace(/[+@]\S+/g, '') .replace(/\s+/g, ' ') .trim(); return { id, text, priority: priority || '', projects, contexts }; } } // CompletedPartの修正 CompletedPart = "x" __ completionDate:Date? __ createdDate:Date? __ rest:RestOfLine { return { completed: true, date: createdDate || "", ...processRest(rest) }; } // UncompletedPartの修正 UncompletedPart = priority:Priority? __ createdDate:Date? __ rest:RestOfLine { return { completed: false, date: createdDate || "", ...processRest(rest, priority) // 優先度を渡す }; } // Priorityルールの改良(空文字列許容) Priority = "(" $[A-Z] ")" { return text().substring(1, 2); } / "" { return ""; } // 優先度なしの場合3. パーサー関数の修正
4. テストケース例
主な修正点
優先度の分離処理
(A)形式をテキストから除去してpriorityフィールドに格納priority: "A",text: "タスク"優先度のないケースの対応
テキストのクリーンアップ
型定義の整合性
Todoインターフェースにpriorityフィールドを追加これで優先度が正しく処理されるようになりました! 🎉