import 'package:todo/cli/cli_args.dart'; import 'package:todo/cli/cli_session.dart'; import 'package:todo/cli/note_lookup.dart'; import 'package:todo/data/note.dart'; import 'package:todo/data/note_repository.dart'; import 'package:todo/sync/sync_service.dart'; import 'package:uuid/uuid.dart'; /// One CLI command per method over an open [CliSession]; each returns the /// exit code. Id arguments are resolved by [findNote]. class CliCommands { /// Writes results to the first sink, errors and warnings to the second. CliCommands(this.session, this._out, this._err); /// The open session the commands act on. final CliSession session; final StringSink _out; final StringSink _err; /// Creates a note (a repo task when `--repo` is set) and pushes it. Future add(CliArgs args, String text) async { final now = DateTime.now(); final note = Note( id: const Uuid().v4(), text: text, priority: args.priority, status: args.status ?? Status.todo, createdAt: now, updatedAt: now, repo: args.opts.repo, doneCheck: args.opts.doneCheck, by: args.opts.by, ); await session.repository.upsert(note); if (!await _push('note')) return 1; _out.writeln(note.id); return 0; } /// Prints [listLine] for each note matching the filters in [args]. Future list(CliArgs args) async { _report(await session.sync(push: false)); final notes = await session.repository.listNotes( filter: NoteFilter( query: args.opts.query ?? '', statuses: {?args.status}, sources: {?args.opts.repo}, ), ); notes.map(listLine).forEach(_out.writeln); return 0; } /// Prints one note in full. Future get(String ref) async { _report(await session.sync(push: false)); final note = findNote(await session.repository.listNotes(), ref); _out ..writeln('id: ${note.id}') ..writeln('priority: ${note.priority.name}') ..writeln('status: ${CliArgs.statusName(note.status)}') ..writeln('created: ${note.createdAt.toIso8601String()}') ..writeln('updated: ${note.updatedAt.toIso8601String()}'); for (final (name, value) in [ ('repo', note.repo), ('done_check', note.doneCheck), ('by', note.by), ('evidence', note.evidence), ]) { if (value != null) _out.writeln('$name: $value'); } _out ..writeln('---') ..writeln(note.text); return 0; } /// Tombstones a note and pushes the deletion. Future delete(String ref) async { final note = await _pullAndFind(ref); await session.repository.delete(note.id); if (!await _push('deletion')) return 1; _out.writeln('deleted ${note.id}'); return 0; } /// Changes a note's status and pushes it. Future setStatus(String ref, Status status) async { final note = await _pullAndFind(ref); await _save(note.copyWith(status: status)); if (!await _push('status change')) return 1; _out.writeln('${note.id}\t${CliArgs.statusName(status)}'); return 0; } /// Marks a note done with [evidence], keeping it, and pushes it. Future close(String ref, String evidence) async { final note = await _pullAndFind(ref); await _save(note.copyWith(status: Status.done, evidence: evidence)); if (!await _push('close')) return 1; _out.writeln('closed ${note.id}'); return 0; } Future _pullAndFind(String ref) async { _report(await session.sync(push: true)); return findNote(await session.repository.listNotes(), ref); } Future _save(Note note) => session.repository.upsert(note.copyWith(updatedAt: DateTime.now())); Future _push(String what) async { if (_report(await session.sync(push: true)).pushed) return true; _err.writeln('error: $what saved locally but nothing was pushed'); return false; } SyncResult _report(SyncResult result) { for (final name in result.skippedFiles) { _err.writeln('warning: could not merge peer file $name'); } return result; } }