@typescript-calendar-lib/cli 0.2.2 → 0.2.6

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.
package/src/align.ts DELETED
@@ -1,35 +0,0 @@
1
- // ─── テキスト整列 ────────────────────────────────────────
2
-
3
- /** 東アジアの全角文字(ターミナル表示幅2列) */
4
- const WIDE =
5
- /[\u1100-\u115f\u2e80-\u303e\u3041-\u33ff\u3400-\u4dbf\u4e00-\u9fff\ua000-\ua4cf\ua960-\ua97f\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6]/;
6
-
7
- /** ターミナル上の表示幅を返す(全角文字は2列として数える) */
8
- export function displayWidth(text: string): number {
9
- let width = 0;
10
- for (const ch of text) {
11
- width += WIDE.test(ch) ? 2 : 1;
12
- }
13
- return width;
14
- }
15
-
16
- /** 表示幅 width に右詰めする(全角文字対応) */
17
- export function padStartWidth(text: string, width: number): string {
18
- return " ".repeat(Math.max(0, width - displayWidth(text))) + text;
19
- }
20
-
21
- /** タイトルを幅の中央に揃える */
22
- export function centerText(text: string, width: number): string {
23
- const padding = Math.max(0, Math.floor((width - displayWidth(text)) / 2));
24
- return " ".repeat(padding) + text;
25
- }
26
-
27
- /** タイトルを中央に揃え、幅一杯まで埋める(枠内用) */
28
- export function centerTextFull(text: string, width: number): string {
29
- const padding = Math.max(0, Math.floor((width - displayWidth(text)) / 2));
30
- return (
31
- " ".repeat(padding) +
32
- text +
33
- " ".repeat(Math.max(0, width - padding - displayWidth(text)))
34
- );
35
- }
package/src/ansi.ts DELETED
@@ -1,18 +0,0 @@
1
- // ─── ANSI 色付け ─────────────────────────────────────────
2
-
3
- const ANSI_PATTERN = new RegExp(`${"\u001b"}\\[[0-9;]*m`, "g");
4
-
5
- /** ANSI コードを付与する(code が undefined ならそのまま) */
6
- export function colorize(
7
- text: string,
8
- code: number | undefined,
9
- enabled: boolean,
10
- ): string {
11
- if (!enabled || code === undefined) return text;
12
- return `\u001b[${code}m${text}\u001b[0m`;
13
- }
14
-
15
- /** ANSI エスケープシーケンスを除去した表示幅を返す */
16
- export function visibleWidth(text: string): number {
17
- return text.replace(ANSI_PATTERN, "").length;
18
- }
package/src/bin.ts DELETED
@@ -1,234 +0,0 @@
1
- #!/usr/bin/env bun
2
- import type {
3
- HighlightStyle,
4
- Locale,
5
- WeekStart,
6
- } from "@typescript-calendar-lib/core";
7
- import { calendar } from "./calendar.ts";
8
- import type { ColorSchemeName, ThemeName } from "./theme.ts";
9
-
10
- const THEMES: readonly ThemeName[] = ["default", "modern"];
11
- const COLOR_SCHEMES: readonly ColorSchemeName[] = [
12
- "default",
13
- "ocean",
14
- "forest",
15
- "sunset",
16
- "mono",
17
- ];
18
- const LOCALES: readonly Locale[] = ["en", "ja", "es", "de", "fr", "ko", "zh"];
19
- const WEEK_STARTS: readonly WeekStart[] = ["sunday", "monday"];
20
- const HIGHLIGHT_STYLES: readonly HighlightStyle[] = ["bracket", "reverse"];
21
-
22
- export interface CliArgs {
23
- year?: number;
24
- month?: number;
25
- theme?: ThemeName;
26
- colorScheme?: ColorSchemeName;
27
- color?: boolean;
28
- locale?: Locale;
29
- weekStart?: WeekStart;
30
- highlight?: Date;
31
- highlightStyle?: HighlightStyle;
32
- }
33
-
34
- export interface ParseResult {
35
- args: CliArgs;
36
- error?: string;
37
- help?: boolean;
38
- }
39
-
40
- /** YYYY-MM-DD 形式の日付文字列をパースする */
41
- export function parseDate(value: string): Date | null {
42
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
43
- if (!match) return null;
44
- const year = Number(match[1]);
45
- const month = Number(match[2]);
46
- const day = Number(match[3]);
47
- if (month < 1 || month > 12) return null;
48
- if (day < 1 || day > 31) return null;
49
- const date = new Date(year, month - 1, day);
50
- if (
51
- date.getFullYear() !== year ||
52
- date.getMonth() !== month - 1 ||
53
- date.getDate() !== day
54
- )
55
- return null;
56
- return date;
57
- }
58
-
59
- /** 指定した候補リストに値が含まれるか検証する */
60
- function validateChoice<T extends string>(
61
- value: T | undefined,
62
- choices: readonly T[],
63
- name: string,
64
- ): string | undefined {
65
- if (value === undefined) return undefined;
66
- if (!choices.includes(value)) {
67
- return `Invalid ${name}: "${value}" (expected: ${choices.join(" | ")})`;
68
- }
69
- return undefined;
70
- }
71
-
72
- /**
73
- * CLI 引数をパースする。テスト可能なように副作用を分離している。
74
- */
75
- export function parseArgs(args: readonly string[]): ParseResult {
76
- const result: CliArgs = {};
77
- const missing = (name: string): ParseResult => ({
78
- args: result,
79
- error: `Missing value for option: ${name}`,
80
- });
81
-
82
- for (let i = 0; i < args.length; i++) {
83
- const arg = args[i]!;
84
- const next = (): string | undefined => args[++i];
85
-
86
- switch (arg) {
87
- case "-h":
88
- case "--help":
89
- return { args: result, help: true };
90
- case "--theme": {
91
- const value = next();
92
- if (value === undefined) return missing("--theme");
93
- result.theme = value as ThemeName;
94
- break;
95
- }
96
- case "--color-scheme": {
97
- const value = next();
98
- if (value === undefined) return missing("--color-scheme");
99
- result.colorScheme = value as ColorSchemeName;
100
- break;
101
- }
102
- case "--color":
103
- result.color = true;
104
- break;
105
- case "--locale": {
106
- const value = next();
107
- if (value === undefined) return missing("--locale");
108
- result.locale = value as Locale;
109
- break;
110
- }
111
- case "--week-start": {
112
- const value = next();
113
- if (value === undefined) return missing("--week-start");
114
- result.weekStart = value as WeekStart;
115
- break;
116
- }
117
- case "--highlight": {
118
- const value = next();
119
- if (value === undefined) return missing("--highlight");
120
- const parsed = parseDate(value);
121
- if (parsed === null) {
122
- return {
123
- args: result,
124
- error: `Invalid --highlight date: "${value}" (expected YYYY-MM-DD, e.g. 2026-09-08)`,
125
- };
126
- }
127
- result.highlight = parsed;
128
- break;
129
- }
130
- case "--highlight-style": {
131
- const value = next();
132
- if (value === undefined) return missing("--highlight-style");
133
- result.highlightStyle = value as HighlightStyle;
134
- break;
135
- }
136
- default: {
137
- if (arg.startsWith("-")) {
138
- return { args: result, error: `Unknown option: ${arg}` };
139
- }
140
- if (!/^\d+$/.test(arg)) {
141
- return {
142
- args: result,
143
- error: `Invalid argument: "${arg}" (expected a year or month number)`,
144
- };
145
- }
146
- if (result.year === undefined) {
147
- result.year = Number(arg);
148
- } else if (result.month === undefined) {
149
- result.month = Number(arg);
150
- } else {
151
- return { args: result, error: `Too many arguments: "${arg}"` };
152
- }
153
- }
154
- }
155
- }
156
-
157
- // 値のバリデーション
158
- if (result.year !== undefined && (result.year < 1 || result.year > 9999)) {
159
- return {
160
- args: result,
161
- error: `Invalid year: ${result.year} (expected 1–9999)`,
162
- };
163
- }
164
- if (result.month !== undefined && (result.month < 1 || result.month > 12)) {
165
- return {
166
- args: result,
167
- error: `Invalid month: ${result.month} (expected 1–12)`,
168
- };
169
- }
170
-
171
- for (const [value, choices, name] of [
172
- [result.theme, THEMES, "theme"] as const,
173
- [result.colorScheme, COLOR_SCHEMES, "color-scheme"] as const,
174
- [result.locale, LOCALES, "locale"] as const,
175
- [result.weekStart, WEEK_STARTS, "week-start"] as const,
176
- [result.highlightStyle, HIGHLIGHT_STYLES, "highlight-style"] as const,
177
- ]) {
178
- const error = validateChoice(value, choices, name);
179
- if (error) return { args: result, error };
180
- }
181
-
182
- return { args: result };
183
- }
184
-
185
- export function printUsage(): string {
186
- return `Usage: typescript-calendar-lib [YYYY] [MM] [options]
187
-
188
- typescript-calendar-lib Render the current month
189
- typescript-calendar-lib 2026 Render the current month of 2026
190
- typescript-calendar-lib 2026 9 Render September 2026
191
-
192
- Options:
193
- --theme <name> Look: default | modern (default: default)
194
- --color-scheme <name> Colors: default | ocean | forest | sunset | mono
195
- --color Enable ANSI colors
196
- --locale <lang> Language: en | ja | es | de | fr | ko | zh (default: en)
197
- --week-start <day> First weekday: sunday | monday (default: sunday)
198
- --highlight <YYYY-MM-DD> Highlight a date (e.g. 2026-09-08)
199
- --highlight-style <style> Highlight style: bracket | reverse (default: bracket)
200
- -h, --help Show this help
201
- `;
202
- }
203
-
204
- if (import.meta.main) {
205
- const { args, error, help } = parseArgs(process.argv.slice(2));
206
-
207
- if (help) {
208
- console.log(printUsage());
209
- process.exit(0);
210
- }
211
- if (error) {
212
- console.error(`Error: ${error}`);
213
- console.error(printUsage());
214
- process.exit(1);
215
- }
216
-
217
- const now = new Date();
218
- const year = args.year ?? now.getFullYear();
219
- const month = args.month ?? now.getMonth() + 1;
220
-
221
- console.log(
222
- calendar({
223
- year,
224
- month,
225
- theme: args.theme,
226
- colorScheme: args.colorScheme,
227
- color: args.color,
228
- locale: args.locale,
229
- weekStart: args.weekStart,
230
- highlight: args.highlight,
231
- highlightStyle: args.highlightStyle,
232
- }),
233
- );
234
- }
package/src/border.ts DELETED
@@ -1,37 +0,0 @@
1
- import type { FrameChars } from "./theme.ts";
2
-
3
- // ─── 枠線 ─────────────────────────────────────────────────
4
-
5
- /** 枠内のコンテンツ幅(例: 7列×3幅+区切り6 = 27) */
6
- export function innerWidth(cellWidth: number, cols: number): number {
7
- return cols * cellWidth + (cols - 1);
8
- }
9
-
10
- /** 上枠: ┌────┬────...────┐ */
11
- export function topBorder(
12
- frame: FrameChars,
13
- cellWidth: number,
14
- cols: number,
15
- ): string {
16
- return `${frame.topLeft}${frame.h.repeat(innerWidth(cellWidth, cols))}${frame.topRight}`;
17
- }
18
-
19
- /** 下枠: └────┴────...────┘ */
20
- export function bottomBorder(
21
- frame: FrameChars,
22
- cellWidth: number,
23
- cols: number,
24
- ): string {
25
- const segments = Array<string>(cols).fill(frame.h.repeat(cellWidth));
26
- return `${frame.bottomLeft}${segments.join(frame.footJ)}${frame.bottomRight}`;
27
- }
28
-
29
- /** 区切り行: ├────┬────...┬────┤ */
30
- export function separatorRow(
31
- frame: FrameChars,
32
- cellWidth: number,
33
- cols: number,
34
- ): string {
35
- const segments = Array<string>(cols).fill(frame.h.repeat(cellWidth));
36
- return `├${segments.join(frame.j)}┤`;
37
- }
package/src/calendar.ts DELETED
@@ -1,70 +0,0 @@
1
- import { getMonthRange } from "@typescript-calendar-lib/core";
2
- import { renderMonth, renderYear } from "./render.ts";
3
- import type {
4
- CalendarOptions,
5
- CalendarRangeOptions,
6
- CalendarYearOptions,
7
- RenderMonthOptions,
8
- } from "./types.ts";
9
-
10
- // ─── Options 解決 ─────────────────────────────────────────
11
-
12
- /**
13
- * 各 API に共通のオプションをデフォルト値込みの描画オプションに正規化する。
14
- * core 由来のオプションと CLI 固有のオプション(theme 等)をまとめる。
15
- */
16
- function toRenderOptions(
17
- options: CalendarOptions | CalendarYearOptions | CalendarRangeOptions,
18
- ): RenderMonthOptions {
19
- const {
20
- locale = "en",
21
- weekStart = "sunday",
22
- highlight,
23
- highlightStyle = "bracket",
24
- range,
25
- color = false,
26
- theme,
27
- colorScheme,
28
- today,
29
- } = options;
30
-
31
- return {
32
- locale,
33
- weekStart,
34
- highlight,
35
- highlightStyle,
36
- range,
37
- color,
38
- theme,
39
- colorScheme,
40
- today,
41
- };
42
- }
43
-
44
- // ─── 公開API ─────────────────────────────────────────────
45
-
46
- /**
47
- * 月カレンダーをテキストで返す
48
- */
49
- export function calendar(options: CalendarOptions): string {
50
- return renderMonth(options.year, options.month, toRenderOptions(options));
51
- }
52
-
53
- /**
54
- * 年間カレンダーを4列×3行でテキストで返す
55
- */
56
- export function calendarYear(options: CalendarYearOptions): string {
57
- return renderYear(options.year, toRenderOptions(options));
58
- }
59
-
60
- /**
61
- * 任意の日付範囲のカレンダーをテキストで返す
62
- */
63
- export function calendarRange(options: CalendarRangeOptions): string {
64
- const renderOptions = toRenderOptions(options);
65
- const months = getMonthRange(options.from, options.to);
66
-
67
- return months
68
- .map(({ year, month }) => renderMonth(year, month, renderOptions))
69
- .join("\n\n");
70
- }
package/src/index.ts DELETED
@@ -1,24 +0,0 @@
1
- export {
2
- calendar,
3
- calendarRange,
4
- calendarYear,
5
- } from "./calendar.ts";
6
- export type {
7
- CliPalette,
8
- CliTheme,
9
- ColorSchemeName,
10
- FrameChars,
11
- ThemeName,
12
- } from "./theme.ts";
13
- export {
14
- COLOR_SCHEMES,
15
- resolveColorScheme,
16
- resolveTheme,
17
- THEMES,
18
- } from "./theme.ts";
19
- export type {
20
- CalendarOptions,
21
- CalendarRangeOptions,
22
- CalendarYearOptions,
23
- RenderMonthOptions,
24
- } from "./types.ts";
package/src/month.ts DELETED
@@ -1,207 +0,0 @@
1
- import {
2
- buildMonthGrid,
3
- getMonthName,
4
- getWeekdayHeaders,
5
- isDateInRange,
6
- isSameDay,
7
- } from "@typescript-calendar-lib/core";
8
- import { centerText, centerTextFull, padStartWidth } from "./align.ts";
9
- import { colorize } from "./ansi.ts";
10
- import { bottomBorder, innerWidth, separatorRow, topBorder } from "./border.ts";
11
- import type { CliPalette } from "./theme.ts";
12
- import { resolveColorScheme, resolveTheme } from "./theme.ts";
13
- import type { RenderMonthOptions } from "./types.ts";
14
-
15
- const CELL_WIDTH = 3;
16
-
17
- /**
18
- * 1ヶ月分のカレンダーテキストを描画する
19
- */
20
- export function renderMonth(
21
- year: number,
22
- month: number,
23
- options: RenderMonthOptions = {},
24
- ): string {
25
- const {
26
- locale = "en",
27
- weekStart = "sunday",
28
- highlight,
29
- highlightStyle = "bracket",
30
- range,
31
- color = false,
32
- theme: themeOption = "default",
33
- colorScheme: schemeOption = "default",
34
- today = new Date(),
35
- } = options;
36
-
37
- const theme = resolveTheme(themeOption);
38
- const palette = resolveColorScheme(schemeOption);
39
-
40
- const title = `${getMonthName(locale, month)} ${year}`;
41
- const weekdays = getWeekdayHeaders(locale, weekStart);
42
- const grid = buildMonthGrid(year, month, weekStart);
43
-
44
- // bracket ハイライトは2桁の日付で `[10]` の4文字になるため、
45
- // ハイライト中は全セルを4列に揃えて列ずれを防ぐ
46
- const cellWidth =
47
- highlight !== undefined && highlightStyle === "bracket"
48
- ? CELL_WIDTH + 1
49
- : CELL_WIDTH;
50
-
51
- const lines: string[] = [];
52
-
53
- if (theme.frame === null) {
54
- // ── 枠なし(default) ──
55
- const sep = theme.separator;
56
- const totalWidth =
57
- weekdays.length * cellWidth + (weekdays.length - 1) * sep.length;
58
-
59
- lines.push(centerText(title, totalWidth));
60
-
61
- lines.push(
62
- weekdays
63
- .map((d) =>
64
- colorize(padStartWidth(d, cellWidth), palette.weekday, color),
65
- )
66
- .join(sep),
67
- );
68
-
69
- for (const row of grid) {
70
- if (row.every((d) => d === null)) continue;
71
-
72
- const cells = row.map((day) =>
73
- renderCell(
74
- year,
75
- month,
76
- day,
77
- highlight,
78
- highlightStyle,
79
- range,
80
- today,
81
- color,
82
- palette,
83
- cellWidth,
84
- ),
85
- );
86
-
87
- lines.push(cells.join(sep));
88
- }
89
- } else {
90
- // ── 枠あり(modern) ──
91
- const frame = theme.frame;
92
-
93
- lines.push(
94
- colorize(
95
- topBorder(frame, cellWidth, weekdays.length),
96
- palette.frame,
97
- color,
98
- ),
99
- );
100
- lines.push(
101
- colorize(
102
- `${frame.v}${centerTextFull(title, innerWidth(cellWidth, weekdays.length))}${frame.v}`,
103
- palette.title,
104
- color,
105
- ),
106
- );
107
- lines.push(
108
- colorize(
109
- separatorRow(frame, cellWidth, weekdays.length),
110
- palette.frame,
111
- color,
112
- ),
113
- );
114
- lines.push(
115
- colorize(
116
- `${frame.v}${weekdays.map((d) => padStartWidth(d, cellWidth)).join(frame.v)}${frame.v}`,
117
- palette.weekday,
118
- color,
119
- ),
120
- );
121
- lines.push(
122
- colorize(
123
- separatorRow(frame, cellWidth, weekdays.length),
124
- palette.frame,
125
- color,
126
- ),
127
- );
128
-
129
- for (const row of grid) {
130
- if (row.every((d) => d === null)) continue;
131
-
132
- const cells = row.map((day) =>
133
- renderCell(
134
- year,
135
- month,
136
- day,
137
- highlight,
138
- highlightStyle,
139
- range,
140
- today,
141
- color,
142
- palette,
143
- cellWidth,
144
- ),
145
- );
146
-
147
- lines.push(`${frame.v}${cells.join(frame.v)}${frame.v}`);
148
- }
149
-
150
- lines.push(
151
- colorize(
152
- bottomBorder(frame, cellWidth, weekdays.length),
153
- palette.frame,
154
- color,
155
- ),
156
- );
157
- }
158
-
159
- return lines.join("\n");
160
- }
161
-
162
- // ─── セル ─────────────────────────────────────────────────
163
-
164
- function renderCell(
165
- year: number,
166
- month: number,
167
- day: number | null,
168
- highlight: Date | undefined,
169
- highlightStyle: "bracket" | "reverse",
170
- range: { from: Date; to: Date } | undefined,
171
- today: Date,
172
- color: boolean,
173
- palette: CliPalette,
174
- cellWidth: number,
175
- ): string {
176
- if (day === null) {
177
- return " ".repeat(cellWidth);
178
- }
179
-
180
- const date = new Date(year, month - 1, day);
181
- const isHighlight = highlight !== undefined && isSameDay(date, highlight);
182
- const isInRange = isDateInRange(date, range);
183
- const isToday = isSameDay(date, today);
184
- const isWeekend = date.getDay() === 0 || date.getDay() === 6;
185
-
186
- let text: string;
187
- if (isHighlight && highlightStyle === "bracket") {
188
- text = `[${day}]`.padStart(cellWidth);
189
- } else {
190
- text = String(day).padStart(cellWidth);
191
- }
192
-
193
- let code: number | undefined;
194
- if (isHighlight && highlightStyle === "reverse") {
195
- code = palette.highlight ?? 7;
196
- } else if (isInRange && !isHighlight) {
197
- code = palette.range ?? 33;
198
- } else if (isToday && palette.today !== undefined) {
199
- code = palette.today;
200
- } else if (isWeekend && palette.weekend !== undefined) {
201
- code = palette.weekend;
202
- } else if (palette.day !== undefined) {
203
- code = palette.day;
204
- }
205
-
206
- return colorize(text, code, color);
207
- }
package/src/render.ts DELETED
@@ -1,5 +0,0 @@
1
- // レンダリングは責務ごとに ansi / align / border / month / year に分割されている。
2
- // このファイルは互換性のため公開 API を再エクスポートする。
3
-
4
- export { renderMonth } from "./month.ts";
5
- export { renderYear } from "./year.ts";