todoosy 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Todoosy Formatter
3
+ */
4
+ import type { Scheme } from './types.js';
5
+ export declare function format(text: string, scheme?: Scheme, filename?: string): string;
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ /**
3
+ * Todoosy Formatter
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.format = format;
7
+ const parser_js_1 = require("./parser.js");
8
+ function parseMiscLocation(misc) {
9
+ const slashIndex = misc.indexOf('/');
10
+ if (slashIndex === -1) {
11
+ return { filename: misc, heading: 'Misc' };
12
+ }
13
+ return {
14
+ filename: misc.substring(0, slashIndex),
15
+ heading: misc.substring(slashIndex + 1),
16
+ };
17
+ }
18
+ function formatMetadata(metadata) {
19
+ const parts = [];
20
+ if (metadata.due) {
21
+ const softPrefix = metadata.due_soft ? '~' : '';
22
+ parts.push(`due ${softPrefix}${metadata.due}`);
23
+ }
24
+ if (metadata.progress) {
25
+ parts.push(metadata.progress);
26
+ }
27
+ if (metadata.priority !== null) {
28
+ parts.push(`p${metadata.priority}`);
29
+ }
30
+ if (metadata.estimate_minutes !== null) {
31
+ const minutes = metadata.estimate_minutes;
32
+ if (minutes % 480 === 0 && minutes >= 480) {
33
+ parts.push(`${minutes / 480}d`);
34
+ }
35
+ else if (minutes % 60 === 0 && minutes >= 60) {
36
+ parts.push(`${minutes / 60}h`);
37
+ }
38
+ else {
39
+ parts.push(`${minutes}m`);
40
+ }
41
+ }
42
+ // Output direct hashtags (not effective_hashtags) at end
43
+ for (const tag of metadata.hashtags) {
44
+ parts.push(`#${tag}`);
45
+ }
46
+ return parts.length > 0 ? `(${parts.join(' ')})` : '';
47
+ }
48
+ function formatItemLine(item, indent = 0, sequenceNum) {
49
+ const indentStr = ' '.repeat(indent);
50
+ const metaStr = formatMetadata(item.metadata);
51
+ const titleWithMeta = metaStr ? `${item.title_text} ${metaStr}` : item.title_text;
52
+ if (item.type === 'heading') {
53
+ const hashes = '#'.repeat(item.level || 1);
54
+ return `${hashes} ${titleWithMeta}`;
55
+ }
56
+ // Use numbered marker if item is numbered
57
+ if (item.marker_type === 'numbered' && sequenceNum !== undefined) {
58
+ return `${indentStr}${sequenceNum}. ${titleWithMeta}`;
59
+ }
60
+ return `${indentStr}- ${titleWithMeta}`;
61
+ }
62
+ function formatComments(comments, isListItem, indent) {
63
+ if (comments.length === 0)
64
+ return [];
65
+ if (isListItem) {
66
+ // Indent is the list item's indent level; comments need +1 level
67
+ const indentStr = ' '.repeat(indent + 1);
68
+ return comments.map(c => `${indentStr}${c}`);
69
+ }
70
+ // Heading comments are not indented
71
+ return comments;
72
+ }
73
+ function format(text, scheme, filename) {
74
+ const { ast } = (0, parser_js_1.parse)(text);
75
+ const lines = [];
76
+ const itemMap = new Map();
77
+ for (const item of ast.items) {
78
+ itemMap.set(item.id, item);
79
+ }
80
+ // Determine misc location from scheme or use default
81
+ const miscLocation = parseMiscLocation(scheme?.misc ?? 'todoosy.md/Misc');
82
+ // If no filename provided, assume it could be the misc file (backward compatibility)
83
+ const isMiscFile = filename === undefined || filename === miscLocation.filename;
84
+ // Determine formatting style: roomy (default), balanced, or tight
85
+ const formattingStyle = scheme?.formatting_style ?? 'roomy';
86
+ // Track Misc section
87
+ let miscSectionId = null;
88
+ // Find existing Misc section (using configured heading name)
89
+ for (const item of ast.items) {
90
+ if (item.type === 'heading' && item.title_text === miscLocation.heading && item.level === 1) {
91
+ miscSectionId = item.id;
92
+ break;
93
+ }
94
+ }
95
+ // Helper to determine if we should add blank line before a heading
96
+ function shouldAddBlankBefore(item) {
97
+ if (item.type !== 'heading')
98
+ return false;
99
+ if (formattingStyle === 'tight')
100
+ return false;
101
+ if (formattingStyle === 'balanced')
102
+ return item.level === 1;
103
+ return true; // roomy
104
+ }
105
+ // Helper to determine if we should add blank line after a heading
106
+ function shouldAddBlankAfter(item) {
107
+ if (item.type !== 'heading')
108
+ return false;
109
+ if (formattingStyle === 'tight')
110
+ return false;
111
+ if (formattingStyle === 'balanced')
112
+ return item.level === 1;
113
+ return true; // roomy
114
+ }
115
+ // Helper to format an item and its subtree
116
+ function formatItem(id, listIndent = 0, isUnderMisc = false, sequenceNum) {
117
+ const item = itemMap.get(id);
118
+ // Skip Misc section during normal iteration (we'll add it at the end)
119
+ if (item.id === miscSectionId && !isUnderMisc) {
120
+ return;
121
+ }
122
+ // Add blank line before headings (except at start), based on style
123
+ if (shouldAddBlankBefore(item) && lines.length > 0) {
124
+ if (lines[lines.length - 1] !== '') {
125
+ lines.push('');
126
+ }
127
+ }
128
+ lines.push(formatItemLine(item, listIndent, sequenceNum));
129
+ // Add blank line after heading before comments or children, based on style
130
+ if (shouldAddBlankAfter(item)) {
131
+ lines.push('');
132
+ }
133
+ // Add comments
134
+ const formattedComments = formatComments(item.comments, item.type === 'list', listIndent);
135
+ lines.push(...formattedComments);
136
+ // Add blank line after heading comments before children
137
+ if (item.type === 'heading' && item.comments.length > 0 && item.children.length > 0) {
138
+ if (shouldAddBlankAfter(item)) {
139
+ lines.push('');
140
+ }
141
+ }
142
+ // Format children - renumber sequenced items
143
+ let childSequenceNum = 1;
144
+ for (const childId of item.children) {
145
+ const child = itemMap.get(childId);
146
+ if (child.type === 'list') {
147
+ // List items under a heading start at indent 0
148
+ // Nested list items increment indent
149
+ const nextIndent = item.type === 'heading' ? 0 : listIndent + 1;
150
+ // Pass sequence number for numbered items and increment
151
+ const childSeq = child.marker_type === 'numbered' ? childSequenceNum++ : undefined;
152
+ formatItem(childId, nextIndent, isUnderMisc, childSeq);
153
+ }
154
+ else {
155
+ formatItem(childId, 0, isUnderMisc);
156
+ }
157
+ }
158
+ }
159
+ // Format all root items except Misc
160
+ for (const rootId of ast.root_ids) {
161
+ if (rootId !== miscSectionId) {
162
+ formatItem(rootId, 0, false);
163
+ }
164
+ }
165
+ // Add Misc section at the end (only for the misc file)
166
+ if (isMiscFile) {
167
+ // Add blank line before Misc heading based on style
168
+ const miscItem = miscSectionId ? itemMap.get(miscSectionId) : null;
169
+ const shouldAddBlankBeforeMisc = formattingStyle !== 'tight' && (formattingStyle === 'roomy' || formattingStyle === 'balanced');
170
+ if (lines.length > 0 && lines[lines.length - 1] !== '' && shouldAddBlankBeforeMisc) {
171
+ lines.push('');
172
+ }
173
+ lines.push(`# ${miscLocation.heading}`);
174
+ // Add blank line after Misc heading based on style
175
+ const shouldAddBlankAfterMisc = formattingStyle !== 'tight' && (formattingStyle === 'roomy' || formattingStyle === 'balanced');
176
+ // Add Misc items if they exist
177
+ if (miscSectionId) {
178
+ const miscItemNode = itemMap.get(miscSectionId);
179
+ if (miscItemNode.comments.length > 0) {
180
+ if (shouldAddBlankAfterMisc) {
181
+ lines.push('');
182
+ }
183
+ lines.push(...miscItemNode.comments);
184
+ }
185
+ if (miscItemNode.children.length > 0) {
186
+ if (shouldAddBlankAfterMisc) {
187
+ lines.push('');
188
+ }
189
+ let miscChildSeqNum = 1;
190
+ for (const childId of miscItemNode.children) {
191
+ // Format misc children - they start at indent 0
192
+ const child = itemMap.get(childId);
193
+ const childSeq = child.marker_type === 'numbered' ? miscChildSeqNum++ : undefined;
194
+ lines.push(formatItemLine(child, 0, childSeq));
195
+ const formattedComments = formatComments(child.comments, child.type === 'list', 0);
196
+ lines.push(...formattedComments);
197
+ }
198
+ }
199
+ }
200
+ }
201
+ return lines.join('\n') + '\n';
202
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Todoosy - Markdown-based todo system
3
+ */
4
+ export { parse, parseTokensInParenGroup, extractParenGroups } from './parser.js';
5
+ export type { ParseResult } from './parser.js';
6
+ export { format } from './formatter.js';
7
+ export { lint } from './linter.js';
8
+ export { queryUpcoming, queryMisc, queryByHashtag, listHashtags } from './query.js';
9
+ export type { HashtagItem, HashtagResult, HashtagListResult } from './query.js';
10
+ export { parseScheme, parseSettings } from './settings.js';
11
+ export { analyzeSequence, renumberChildren, insertSequencedItem, removeSequencedItem, convertToSequence, convertToBullets, } from './sequence.js';
12
+ export type { SequenceInfo } from './sequence.js';
13
+ export type { AST, ItemNode, ItemMetadata, Warning, LintResult, UpcomingItem, UpcomingResult, MiscItem, MiscResult, Scheme, Settings, SettingValue, ParsedToken, ParenGroup, } from './types.js';
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * Todoosy - Markdown-based todo system
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.convertToBullets = exports.convertToSequence = exports.removeSequencedItem = exports.insertSequencedItem = exports.renumberChildren = exports.analyzeSequence = exports.parseSettings = exports.parseScheme = exports.listHashtags = exports.queryByHashtag = exports.queryMisc = exports.queryUpcoming = exports.lint = exports.format = exports.extractParenGroups = exports.parseTokensInParenGroup = exports.parse = void 0;
7
+ var parser_js_1 = require("./parser.js");
8
+ Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parser_js_1.parse; } });
9
+ Object.defineProperty(exports, "parseTokensInParenGroup", { enumerable: true, get: function () { return parser_js_1.parseTokensInParenGroup; } });
10
+ Object.defineProperty(exports, "extractParenGroups", { enumerable: true, get: function () { return parser_js_1.extractParenGroups; } });
11
+ var formatter_js_1 = require("./formatter.js");
12
+ Object.defineProperty(exports, "format", { enumerable: true, get: function () { return formatter_js_1.format; } });
13
+ var linter_js_1 = require("./linter.js");
14
+ Object.defineProperty(exports, "lint", { enumerable: true, get: function () { return linter_js_1.lint; } });
15
+ var query_js_1 = require("./query.js");
16
+ Object.defineProperty(exports, "queryUpcoming", { enumerable: true, get: function () { return query_js_1.queryUpcoming; } });
17
+ Object.defineProperty(exports, "queryMisc", { enumerable: true, get: function () { return query_js_1.queryMisc; } });
18
+ Object.defineProperty(exports, "queryByHashtag", { enumerable: true, get: function () { return query_js_1.queryByHashtag; } });
19
+ Object.defineProperty(exports, "listHashtags", { enumerable: true, get: function () { return query_js_1.listHashtags; } });
20
+ var settings_js_1 = require("./settings.js");
21
+ Object.defineProperty(exports, "parseScheme", { enumerable: true, get: function () { return settings_js_1.parseScheme; } });
22
+ Object.defineProperty(exports, "parseSettings", { enumerable: true, get: function () { return settings_js_1.parseSettings; } });
23
+ var sequence_js_1 = require("./sequence.js");
24
+ Object.defineProperty(exports, "analyzeSequence", { enumerable: true, get: function () { return sequence_js_1.analyzeSequence; } });
25
+ Object.defineProperty(exports, "renumberChildren", { enumerable: true, get: function () { return sequence_js_1.renumberChildren; } });
26
+ Object.defineProperty(exports, "insertSequencedItem", { enumerable: true, get: function () { return sequence_js_1.insertSequencedItem; } });
27
+ Object.defineProperty(exports, "removeSequencedItem", { enumerable: true, get: function () { return sequence_js_1.removeSequencedItem; } });
28
+ Object.defineProperty(exports, "convertToSequence", { enumerable: true, get: function () { return sequence_js_1.convertToSequence; } });
29
+ Object.defineProperty(exports, "convertToBullets", { enumerable: true, get: function () { return sequence_js_1.convertToBullets; } });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Todoosy Linter
3
+ */
4
+ import type { LintResult, Scheme } from './types.js';
5
+ export declare function lint(text: string, scheme?: Scheme, filename?: string): LintResult;
6
+ export declare function lintScheme(scheme: Scheme): LintResult;