first commit

This commit is contained in:
Chen Asraf
2021-01-12 02:56:41 +02:00
commit 188819b26c
13 changed files with 772 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# Files and directories created by pub
.dart_tool/
.packages
# Conventional directory for build outputs
build/
# Directory created by dartdoc
doc/api/

17
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "dart_todo",
"request": "launch",
"program": "${workspaceFolder}/bin/todo.dart",
"args": [
"${workspaceFolder}/example.todo"
],
"type": "dart"
}
]
}

3
CHANGELOG.md Normal file
View File

@@ -0,0 +1,3 @@
## 1.0.0
- Initial version, created by Stagehand

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
A sample command-line application with an entrypoint in `bin/`, library code
in `lib/`, and example unit test in `test/`.
Created from templates made available by Stagehand under a BSD-style
[license](https://github.com/dart-lang/stagehand/blob/master/LICENSE).

14
analysis_options.yaml Normal file
View File

@@ -0,0 +1,14 @@
# Defines a default set of lint rules enforced for
# projects at Google. For details and rationale,
# see https://github.com/dart-lang/pedantic#enabled-lints.
include: package:pedantic/analysis_options.yaml
# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
# Uncomment to specify additional rules.
# linter:
# rules:
# - camel_case_types
analyzer:
# exclude:
# - path/to/excluded/files/**

10
bin/todo.dart Normal file
View File

@@ -0,0 +1,10 @@
import 'dart:io';
import 'package:dart_todo/gui.dart';
import 'package:dart_todo/todo.dart';
void main(List<String> arguments) async {
final project = Project(File(arguments.elementAt(0)));
await project.loadTodos();
GUI(project: project);
}

6
example.todo Normal file
View File

@@ -0,0 +1,6 @@
- [ ] Come up with plan
- [ ] Destroy the world
- [ ] Create solution
- [ ] Charge lots of $$$ for solution
- [ ] test
- [ ] another test

1
lib/file_handler.dart Normal file
View File

@@ -0,0 +1 @@
part of 'todo.dart';

155
lib/gui.dart Normal file
View File

@@ -0,0 +1,155 @@
import 'dart:io';
import 'package:dart_console/dart_console.dart';
import 'package:dart_todo/todo.dart';
class GUI {
final Project project;
final Console console;
bool frozen = false;
int activeIndex = 0;
GUI({this.project}) : console = Console() {
stdin.echoMode = false;
stdin.lineMode = false;
stdin.listen(_stdinListener);
_paint();
}
int get lines => stdout.terminalLines;
int get width => stdout.terminalColumns;
int get maxLines => project.todos.length;
String get verticalBorder => '-' * (width - 3);
Todo get currentTodo => project.todos.elementAt(activeIndex);
void _paint() {
if (frozen) {
return;
}
console.clearScreen();
_print(verticalBorder);
_print('');
for (var i = 0; i < project.todos.length; i++) {
final todo = project.todos.elementAt(i);
final selected = i == activeIndex;
final indent = 4;
final line = [
' ' * (selected ? indent - 1 : indent),
selected ? '> ' : ' ',
(todo.done ? '' : '×').padRight(3),
todo.title,
].where((s) => s.isNotEmpty).join(' ');
_print(line);
}
final spareLines = lines - project.todos.length - 6;
if (spareLines > 0) {
for (var i = 0; i < spareLines; i++) {
_print('');
}
}
_print((' ' * 10) + '[enter/space] - toggle | [esc/q] - quit');
_print(verticalBorder);
}
String _ensureLength(String message) =>
'|' + message.padRight(width - '||\n'.length, ' ') + '|\n';
void _print(String message) => console.write(_ensureLength(message));
void _stdinListener(List<int> charCodes) {
if (!frozen) {
_handleGUIInput(keyMap[charCodes.last]);
} else {
final title = String.fromCharCodes(charCodes);
project.addTodo(Todo(title: title));
_exitTextInputMode();
}
_paint();
}
void _handleGUIInput(Key key) {
switch (key) {
case Key.down:
activeIndex++;
if (activeIndex >= maxLines) {
activeIndex = 0;
}
break;
case Key.up:
activeIndex--;
if (activeIndex < 0) {
activeIndex = maxLines - 1;
}
break;
case Key.enter:
case Key.space:
currentTodo.toggle();
break;
case Key.c:
case Key.a:
_enterTextInputMode();
console.write('Enter title: ');
break;
case Key.e:
//TODO: edit current todo
break;
case Key.d:
case Key.backspace:
project.removeTodo(currentTodo);
break;
case Key.q:
case Key.esc:
exit(0);
break;
case Key.left:
case Key.right:
break;
}
}
void _enterTextInputMode() {
frozen = true;
stdin.echoMode = true;
stdin.lineMode = true;
}
void _exitTextInputMode() {
frozen = false;
stdin.echoMode = false;
stdin.lineMode = false;
}
final keyMap = <int, Key>{
32: Key.space,
65: Key.up,
66: Key.down,
67: Key.right,
68: Key.left,
10: Key.enter,
27: Key.esc,
113: Key.q,
123: Key.backspace,
97: Key.a,
99: Key.c,
100: Key.d,
101: Key.e,
};
}
enum Key {
up,
right,
down,
left,
enter,
esc,
space,
backspace,
q,
a,
c,
d,
e,
}

128
lib/todo.dart Normal file
View File

@@ -0,0 +1,128 @@
import 'dart:io';
part 'file_handler.dart';
class Todo {
String _title;
bool _done;
final void Function() onUpdate;
Todo({
String title,
bool done,
this.onUpdate,
}) : _title = title,
_done = done ?? false;
Todo.parseLine(String line, {this.onUpdate}) {
final genericException = FormatException('Todo is malformed', line, 0);
final checkedException = FormatException(
'Todo does not contain "checked" token (v/x)',
line,
line.indexOf(RegExp(r'\[')) + 1);
final pattern = RegExp(r'^- \[[ x]\]');
try {
final match = pattern.matchAsPrefix(line);
if (match == null) {
throw genericException;
}
final split = [
line.substring(0, match.end),
line.substring(match.end + 1),
];
if (split.any((i) => i?.isNotEmpty != true)) {
throw genericException;
}
switch (split[0].replaceAll(' ', '')) {
case '-[]':
_done = false;
break;
case '-[x]':
_done = true;
break;
default:
throw checkedException;
}
_title = split[1];
} on RangeError catch (_) {
throw genericException;
}
}
String get title => _title;
set title(String value) => _update(_title = value);
bool get done => _done;
set done(bool value) => _update(_done = value);
void _update([dynamic _expr]) {
if (_expr is void Function()) {
_expr.call();
}
onUpdate?.call();
}
void toggle() {
done = !done;
}
Todo copyWith({
String title,
bool done,
final void Function() onUpdate,
}) =>
Todo(
title: title ?? this.title,
done: done ?? this.done ?? false,
onUpdate: onUpdate ?? this.onUpdate,
);
String toMarkdown() => '- [${done ? "x" : " "}] $title';
@override
String toString() => '$runtimeType($title, done: $done)';
}
class Project {
List<Todo> todos = [];
File file;
Project(this.file);
void _dumpToFile() {
file.writeAsString(toMarkdown());
}
void create(String name) {
_checkAndCreateFile(File('$name.todo'));
}
void loadTodos() async {
final content = await file.readAsLinesSync();
for (final line in content) {
todos.add(Todo.parseLine(line, onUpdate: _dumpToFile));
}
}
Future<void> _checkAndCreateFile(File file) async {
if (await file.exists() == false) {
await file.create();
}
}
String toMarkdown() => todos.map((t) => t.toMarkdown()).join('\r\n');
void addTodo(Todo todo) {
todos.add(todo.copyWith(onUpdate: _dumpToFile));
_dumpToFile();
}
void removeTodo(Todo todo) {
todos.removeWhere((t) => t.title == todo.title);
_dumpToFile();
}
}

383
pubspec.lock Normal file
View File

@@ -0,0 +1,383 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "14.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "0.41.1"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.0"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.2"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.3"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.13"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
coverage:
dependency: transitive
description:
name: coverage
url: "https://pub.dartlang.org"
source: hosted
version: "0.14.2"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
dart_console:
dependency: "direct main"
description:
name: dart_console
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.2"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.1"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.2"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.4"
intl:
dependency: transitive
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.16.1"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.4"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.2"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "0.11.4"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.9"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.4"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7"
node_interop:
dependency: transitive
description:
name: node_interop
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
node_io:
dependency: transitive
description:
name: node_io
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.12"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
path:
dependency: "direct main"
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
pedantic:
dependency: "direct dev"
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.2"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.4"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.9"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
shelf_static:
dependency: transitive
description:
name: shelf_static
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.9+1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.3"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
source_maps:
dependency: transitive
description:
name: source_maps
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.9"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.6"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
test:
dependency: "direct dev"
description:
name: test
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.7"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.18+1"
test_core:
dependency: transitive
description:
name: test_core
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.11+4"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.0"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+15"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.4"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.4"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
sdks:
dart: ">=2.10.0 <3.0.0"

14
pubspec.yaml Normal file
View File

@@ -0,0 +1,14 @@
name: dart_todo
description: A fully fledged, simple to use to do terminal app
version: 1.0.0
# homepage: https://www.example.com
environment:
sdk: ">=2.10.0 <3.0.0"
dependencies:
dart_console: ^0.6.2
path: ^1.7.0
dev_dependencies:
pedantic: ^1.9.0
test: ^1.14.4

27
test/dart_todo_test.dart Normal file
View File

@@ -0,0 +1,27 @@
import 'package:dart_todo/todo.dart';
import 'package:test/test.dart';
void main() {
test('create todo done', () {
final correctDone = '- [x] I did this';
final todoDone = Todo.parseLine(correctDone);
expect(todoDone.done, equals(true));
expect(todoDone.title, equals('I did this'));
});
test('create todo not done', () {
final correctNotDone = '- [ ] I did not do this';
final todoNotDone = Todo.parseLine(correctNotDone);
expect(todoNotDone.done, equals(false));
expect(todoNotDone.title, equals('I did not do this'));
});
test('create todo with bad format - throws', () {
final incorrect1 = "I don't know if I did this";
final incorrect2 = '- [ ]';
final incorrect3 = "[] I don't know if I did this";
expect(() => Todo.parseLine(incorrect1), throwsFormatException);
expect(() => Todo.parseLine(incorrect2), throwsFormatException);
expect(() => Todo.parseLine(incorrect3), throwsFormatException);
});
}