mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 19:07:02 +01:00
CLI: --set-text replaces a note's text
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Keeps every other field; text from --text or stdin, empty refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
17d6d2a533
commit
a30e7b34a9
@ -63,6 +63,9 @@ class CliArgs {
|
||||
case '--get' || '--delete' || '--close':
|
||||
modes.add(CliMode.values.byName(arg.substring(2)));
|
||||
id = value();
|
||||
case '--set-text':
|
||||
modes.add(CliMode.setText);
|
||||
id = value();
|
||||
case '--set-status':
|
||||
modes.add(CliMode.setStatus);
|
||||
id = value();
|
||||
@ -74,7 +77,8 @@ class CliArgs {
|
||||
if (modes.contains(CliMode.help)) return const CliArgs(mode: CliMode.help);
|
||||
if (modes.length > 1) {
|
||||
throw UsageError(
|
||||
'--list, --search, --get, --delete, --set-status, --close and '
|
||||
'--list, --search, --get, --delete, --set-status, --set-text, '
|
||||
'--close and '
|
||||
'--backfill-created-by are mutually exclusive',
|
||||
);
|
||||
}
|
||||
@ -110,7 +114,8 @@ class CliArgs {
|
||||
/// The selected mode.
|
||||
final CliMode mode;
|
||||
|
||||
/// Note text from `--text`; null means "read stdin" in [CliMode.add].
|
||||
/// Note text from `--text`; null means "read stdin" in [CliMode.add] and
|
||||
/// [CliMode.setText].
|
||||
final String? text;
|
||||
|
||||
/// Full id or 8+ character prefix of the note an id mode acts on.
|
||||
@ -146,7 +151,7 @@ class CliArgs {
|
||||
|
||||
static const _adding = 'when adding a note';
|
||||
static const Map<String, (String, Set<CliMode>)> _scopes = {
|
||||
'--text': (_adding, {CliMode.add}),
|
||||
'--text': ('$_adding or to --set-text', {CliMode.add, CliMode.setText}),
|
||||
'--done-check': (_adding, {CliMode.add}),
|
||||
'--by': (_adding, {CliMode.add}),
|
||||
'--repo': (
|
||||
|
||||
@ -136,6 +136,15 @@ class CliCommands {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Replaces a note's whole text (title is its first line) and pushes it.
|
||||
Future<int> setText(String ref, String text) async {
|
||||
final note = await _pullAndFind(ref);
|
||||
await _save(note.copyWith(text: text));
|
||||
if (!await _push('text change')) return 1;
|
||||
_out.writeln('${note.id}\ttext');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Marks a note done with [evidence], keeping it, and pushes it.
|
||||
Future<int> close(String ref, String evidence) async {
|
||||
final note = await _pullAndFind(ref);
|
||||
|
||||
@ -18,6 +18,9 @@ enum CliMode {
|
||||
/// Change one note's status and push it.
|
||||
setStatus,
|
||||
|
||||
/// Replace one note's whole text and push it.
|
||||
setText,
|
||||
|
||||
/// Set a task done with evidence and push it.
|
||||
close,
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ Future<int> runCli(
|
||||
return 0;
|
||||
}
|
||||
String? text;
|
||||
if (args.mode == CliMode.add) {
|
||||
if (args.mode == CliMode.add || args.mode == CliMode.setText) {
|
||||
text = args.text ?? await readStdin();
|
||||
if (text.trim().isEmpty) {
|
||||
err.writeln('error: empty note (pass --text or pipe text on stdin)');
|
||||
@ -79,6 +79,7 @@ Future<int> runCli(
|
||||
CliMode.list || CliMode.search => await cli.list(args),
|
||||
CliMode.get => await cli.get(args.id!),
|
||||
CliMode.setStatus => await cli.setStatus(args.id!, args.status!),
|
||||
CliMode.setText => await cli.setText(args.id!, text!),
|
||||
CliMode.close => await cli.close(args.id!, args.opts.evidence!),
|
||||
CliMode.backfill => await cli.backfillCreatedBy(
|
||||
claudeNodeIds(env),
|
||||
|
||||
@ -22,6 +22,8 @@ A note with --repo is a repo task; one without is a plain idea.
|
||||
--get <id> pull, then print one note in full (exit 1 if absent)
|
||||
--delete <id> pull, tombstone the note, push
|
||||
--set-status <id> <s> pull, change the note's status (as --status), push
|
||||
--set-text <id> pull, replace the note's whole text with --text (or
|
||||
stdin; first line = title), keep its other fields, push
|
||||
--close <id> --evidence <s>
|
||||
pull, set status done + evidence (commit sha + done
|
||||
check exit code), push; refused without --evidence
|
||||
|
||||
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.71+1
|
||||
version: 1.0.72+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.2
|
||||
|
||||
@ -32,7 +32,11 @@ class CliHarness {
|
||||
|
||||
String dir(String name) => '${tmp.path}/$name';
|
||||
|
||||
Future<int> run(List<String> argv, {SessionOpener? opener}) async {
|
||||
Future<int> run(
|
||||
List<String> argv, {
|
||||
SessionOpener? opener,
|
||||
String stdin = '',
|
||||
}) async {
|
||||
out.clear();
|
||||
err.clear();
|
||||
return runCli(
|
||||
@ -40,7 +44,7 @@ class CliHarness {
|
||||
env: env,
|
||||
out: out,
|
||||
err: err,
|
||||
readStdin: () async => '',
|
||||
readStdin: () async => stdin,
|
||||
open:
|
||||
opener ?? (d, {required seedCredentials}) => open(d, seedCredentials),
|
||||
);
|
||||
|
||||
83
test/cli/cli_set_text_test.dart
Normal file
83
test/cli/cli_set_text_test.dart
Normal file
@ -0,0 +1,83 @@
|
||||
// --set-text: replace a note's whole text, keeping every other field.
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:todo/cli/cli_args.dart';
|
||||
import 'package:todo/cli/cli_mode.dart';
|
||||
import 'package:todo/cli/cli_session.dart';
|
||||
|
||||
import 'cli_harness.dart';
|
||||
import 'scripted_session.dart';
|
||||
|
||||
void main() {
|
||||
late CliHarness h;
|
||||
setUp(() => h = CliHarness());
|
||||
tearDown(() => h.dispose());
|
||||
|
||||
test('--set-text replaces the text and keeps repo and status', () async {
|
||||
final id = await h.add('Old title\nold body', ['--repo', 'kuhyx/todo']);
|
||||
expect(await h.run(['--set-status', id, 'in_progress']), 0);
|
||||
expect(
|
||||
await h.run([
|
||||
'--set-text',
|
||||
id.substring(0, 8),
|
||||
'--text',
|
||||
'New title\nnew body',
|
||||
'--data-dir',
|
||||
h.dir('a'),
|
||||
]),
|
||||
0,
|
||||
);
|
||||
expect(h.out.toString().trim(), '$id\ttext');
|
||||
expect(await h.run(['--get', id, '--data-dir', h.dir('b')]), 0);
|
||||
expect(
|
||||
h.out.toString(),
|
||||
allOf(
|
||||
contains('New title\nnew body'),
|
||||
isNot(contains('old body')),
|
||||
contains('repo: kuhyx/todo'),
|
||||
contains('in_progress'),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('--set-text reads stdin when --text is absent', () async {
|
||||
final id = await h.add('Old');
|
||||
expect(await h.run(['--set-text', id], stdin: 'From stdin'), 0);
|
||||
expect(await h.run(['--get', id, '--data-dir', h.dir('b')]), 0);
|
||||
expect(h.out.toString(), contains('From stdin'));
|
||||
});
|
||||
|
||||
test('--set-text refuses empty text and changes nothing', () async {
|
||||
final id = await h.add('Keep me');
|
||||
expect(await h.run(['--set-text', id, '--text', ' ']), 2);
|
||||
expect(h.err.toString(), contains('empty note'));
|
||||
expect(await h.run(['--get', id]), 0);
|
||||
expect(h.out.toString(), contains('Keep me'));
|
||||
});
|
||||
|
||||
test('--set-text on a missing id exits 1', () async {
|
||||
expect(await h.run(['--set-text', 'ffffffff', '--text', 'x']), 1);
|
||||
expect(h.err.toString(), contains('no live note with id ffffffff'));
|
||||
});
|
||||
|
||||
test('--set-text fails when the push reports nothing published', () async {
|
||||
Future<CliSession> opener(String d, {required String seedCredentials}) =>
|
||||
ScriptedSession.create(h.db.client, [true, false], noteId: 'stuck');
|
||||
expect(
|
||||
await h.run(['--set-text', 'stuck', '--text', 'x'], opener: opener),
|
||||
1,
|
||||
);
|
||||
expect(h.err.toString(), contains('text change saved locally'));
|
||||
});
|
||||
|
||||
test('parse: --set-text takes an id and allows --text', () {
|
||||
final args = CliArgs.parse(['--set-text', 'abc', '--text', 'T']);
|
||||
expect(args.mode, CliMode.setText);
|
||||
expect(args.id, 'abc');
|
||||
expect(args.text, 'T');
|
||||
expect(() => CliArgs.parse(['--set-text']), throwsA(isA<UsageError>()));
|
||||
expect(
|
||||
() => CliArgs.parse(['--set-text', 'a', '--repo', 'kuhyx/todo']),
|
||||
throwsA(isA<UsageError>()),
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user