├── python
├── task_list
│ ├── __init__.py
│ ├── __main__.py
│ ├── task.py
│ ├── console.py
│ └── app.py
├── tests
│ ├── __init__.py
│ └── test_application.py
├── setup.py
├── README.md
└── .gitignore
├── ruby
├── .gitignore
├── Gemfile
├── lib
│ ├── task.rb
│ └── task_list.rb
├── Gemfile.lock
└── spec
│ └── application_spec.rb
├── java
├── .gitignore
├── run
├── src
│ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── codurance
│ │ │ └── training
│ │ │ └── tasks
│ │ │ ├── Task.java
│ │ │ └── TaskList.java
│ └── test
│ │ └── java
│ │ └── com
│ │ └── codurance
│ │ └── training
│ │ └── tasks
│ │ └── ApplicationTest.java
└── pom.xml
├── kotlin
├── .gitignore
├── src
│ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── codurance
│ │ │ └── training
│ │ │ └── tasks
│ │ │ ├── Task.kt
│ │ │ └── TaskList.kt
│ └── test
│ │ └── java
│ │ └── com
│ │ └── codurance
│ │ └── training
│ │ └── tasks
│ │ └── ApplicationTest.java
├── run
└── pom.xml
├── scala
├── .gitignore
├── build.sbt
└── src
│ ├── test
│ └── scala
│ │ └── TaskListSpec.scala
│ └── main
│ └── scala
│ └── TaskList.scala
├── csharp
├── .gitignore
├── Tasks
│ ├── Task.cs
│ ├── Tasks.csproj
│ ├── IConsole.cs
│ ├── RealConsole.cs
│ └── TaskList.cs
├── Tasks.Tests
│ ├── Tasks.Tests.csproj
│ ├── FakeConsole.cs
│ ├── ProducerConsumerStream.cs
│ ├── BlockingStream.cs
│ └── ApplicationTest.cs
└── TaskList.sln
├── golang
├── .gitignore
├── run_tests.sh
├── build_and_run.sh
├── src
│ └── task
│ │ ├── main.go
│ │ ├── task.go
│ │ ├── main_test.go
│ │ └── list.go
└── README.md
├── typescript
├── .gitignore
├── tsd.json
├── src
│ ├── task.ts
│ └── task_list.ts
├── package.json
├── README
└── tests
│ └── application_test.ts
├── .travis.yml
├── LICENSE
└── README.md
/python/task_list/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/python/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ruby/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle
2 |
--------------------------------------------------------------------------------
/java/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | *.iml
3 |
--------------------------------------------------------------------------------
/kotlin/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | *.iml
3 | .idea/
4 |
--------------------------------------------------------------------------------
/scala/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea/
3 | target/
4 |
--------------------------------------------------------------------------------
/csharp/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | bin
3 | obj
4 | packages
5 | *.suo
6 |
--------------------------------------------------------------------------------
/golang/.gitignore:
--------------------------------------------------------------------------------
1 | # temp files
2 | *~
3 | *.orig
4 |
5 | # binaries
6 | /task
7 | /bin/*
8 |
--------------------------------------------------------------------------------
/ruby/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | group :test do
4 | gem 'rspec'
5 | end
6 |
--------------------------------------------------------------------------------
/typescript/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | typings/
3 | src/*.js
4 | src/*.js.map
5 | tests/*.js
6 | tests/*.js.map
7 |
--------------------------------------------------------------------------------
/golang/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | pushd "$(dirname "$0")" > /dev/null
4 |
5 | GOPATH="$PWD":"$GOPATH" go test ./... $@
6 |
7 | popd > /dev/null
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 | install:
5 | - (cd java && mvn install -DskipTests=true)
6 | script:
7 | - (cd java && mvn test)
8 |
--------------------------------------------------------------------------------
/kotlin/src/main/java/com/codurance/training/tasks/Task.kt:
--------------------------------------------------------------------------------
1 | package com.codurance.training.tasks
2 |
3 | class Task(val id: Long, val description: String, var isDone: Boolean)
4 |
--------------------------------------------------------------------------------
/golang/build_and_run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | pushd "$(dirname "$0")" > /dev/null
4 |
5 | GOPATH="$PWD":"$GOPATH" go build -o bin/task task && bin/task
6 |
7 | popd > /dev/null
8 |
--------------------------------------------------------------------------------
/java/run:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | cd "$(dirname "${BASH_SOURCE[0]}")"
6 |
7 | mvn package appassembler:assemble
8 | echo
9 | echo
10 | echo
11 | sh ./target/appassembler/bin/task-list
12 |
--------------------------------------------------------------------------------
/kotlin/run:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | cd "$(dirname "${BASH_SOURCE[0]}")"
6 |
7 | mvn package appassembler:assemble
8 | echo
9 | echo
10 | echo
11 | sh ./target/appassembler/bin/task-list
12 |
--------------------------------------------------------------------------------
/csharp/Tasks/Task.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Tasks
5 | {
6 | public class Task
7 | {
8 | public long Id { get; set; }
9 |
10 | public string Description { get; set; }
11 |
12 | public bool Done { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ruby/lib/task.rb:
--------------------------------------------------------------------------------
1 | class Task
2 | attr_reader :id, :description
3 | attr_accessor :done
4 |
5 | def initialize(id, description, done)
6 | @id = id
7 | @description = description
8 | @done = done
9 | end
10 |
11 | def done?
12 | done
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/csharp/Tasks/Tasks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 |
5 | Exe
6 | Tasks.TaskList
7 |
8 |
9 |
--------------------------------------------------------------------------------
/csharp/Tasks/IConsole.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Tasks
4 | {
5 | public interface IConsole
6 | {
7 | string ReadLine();
8 |
9 | void Write(string format, params object[] args);
10 |
11 | void WriteLine(string format, params object[] args);
12 |
13 | void WriteLine();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/python/task_list/__main__.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from task_list.console import Console
4 | from task_list.app import TaskList
5 |
6 |
7 | def main():
8 | task_list = TaskList(Console(sys.stdin, sys.stdout))
9 | task_list.run()
10 |
11 |
12 | if __name__ == "__main__":
13 | main()
14 |
15 |
--------------------------------------------------------------------------------
/golang/src/task/main.go:
--------------------------------------------------------------------------------
1 | // Package main implements a command-line task manager.
2 | // A manager is a TaskList object, which is started with the Run() function
3 | // and then scans and executes user commands.
4 | package main
5 |
6 | import "os"
7 |
8 | func main() {
9 | NewTaskList(os.Stdin, os.Stdout).Run()
10 | }
11 |
--------------------------------------------------------------------------------
/scala/build.sbt:
--------------------------------------------------------------------------------
1 | lazy val settings = Seq(
2 | name := "tasks",
3 | organization := "com.codurance.training",
4 | version := "0.1-SNAPSHOT",
5 | scalaVersion := "2.11.8"
6 | )
7 |
8 | lazy val root = (project in file(".")).settings(settings: _*)
9 |
10 | libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.6" % Test
11 |
--------------------------------------------------------------------------------
/python/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name='task-list',
5 | version='0.0.1',
6 | packages=['task_list'],
7 | url='https://github.com/codurance/task-list',
8 | description='Task List Kata',
9 | entry_points={
10 | 'console_scripts': [
11 | "task_list = task_list.__main__:main",
12 | ]
13 | }
14 | )
15 |
--------------------------------------------------------------------------------
/python/task_list/task.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | class Task:
4 | def __init__(self, id_: int, description: str, done: bool) -> None:
5 | self.id = id_
6 | self.description = description
7 | self.done = done
8 |
9 | def set_done(self, done: bool) -> None:
10 | self.done = done
11 |
12 | def is_done(self) -> bool:
13 | return self.done
14 |
15 |
--------------------------------------------------------------------------------
/typescript/tsd.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "v4",
3 | "repo": "borisyankov/DefinitelyTyped",
4 | "ref": "master",
5 | "path": "typings",
6 | "bundle": "typings/tsd.d.ts",
7 | "installed": {
8 | "node/node.d.ts": {
9 | "commit": "1c3488c2d84232b9f66b6d7e758839381e95b3bf"
10 | },
11 | "nodeunit/nodeunit.d.ts": {
12 | "commit": "1c3488c2d84232b9f66b6d7e758839381e95b3bf"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/typescript/src/task.ts:
--------------------------------------------------------------------------------
1 |
2 | export class Task
3 | {
4 | constructor(private _id: number, private _description: string, private _done: boolean) {}
5 |
6 | get id() {
7 | return this._id;
8 | }
9 |
10 | get description() {
11 | return this._description;
12 | }
13 |
14 | get done() {
15 | return this._done;
16 | }
17 |
18 | set done(val: boolean) {
19 | this._done = val;
20 | }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/csharp/Tasks/RealConsole.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Tasks
4 | {
5 | public class RealConsole : IConsole
6 | {
7 | public string ReadLine()
8 | {
9 | return Console.ReadLine();
10 | }
11 |
12 | public void Write(string format, params object[] args)
13 | {
14 | Console.Write(format, args);
15 | }
16 |
17 | public void WriteLine(string format, params object[] args)
18 | {
19 | Console.WriteLine(format, args);
20 | }
21 |
22 | public void WriteLine()
23 | {
24 | Console.WriteLine();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ruby/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | diff-lcs (1.2.5)
5 | rspec (3.5.0)
6 | rspec-core (~> 3.5.0)
7 | rspec-expectations (~> 3.5.0)
8 | rspec-mocks (~> 3.5.0)
9 | rspec-core (3.5.1)
10 | rspec-support (~> 3.5.0)
11 | rspec-expectations (3.5.0)
12 | diff-lcs (>= 1.2.0, < 2.0)
13 | rspec-support (~> 3.5.0)
14 | rspec-mocks (3.5.0)
15 | diff-lcs (>= 1.2.0, < 2.0)
16 | rspec-support (~> 3.5.0)
17 | rspec-support (3.5.0)
18 |
19 | PLATFORMS
20 | ruby
21 |
22 | DEPENDENCIES
23 | rspec
24 |
25 | BUNDLED WITH
26 | 1.12.5
27 |
--------------------------------------------------------------------------------
/python/task_list/console.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, IO
2 |
3 |
4 | class Console:
5 | def __init__(self, input_reader: IO, output_writer: IO) -> None:
6 | self.input_reader = input_reader
7 | self.output_writer = output_writer
8 |
9 | def print(self, string: Optional[str]="", end: str="\n", flush: bool=True) -> None:
10 | self.output_writer.write(string + end)
11 | if flush:
12 | self.output_writer.flush()
13 |
14 | def input(self, prompt: Optional[str]="") -> str:
15 | self.print(prompt, end="")
16 | return self.input_reader.readline().strip()
17 |
18 |
19 |
--------------------------------------------------------------------------------
/typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "task-list",
3 | "version": "0.0.0",
4 | "description": "task-list primitives exercise ported to typescript",
5 | "main": "task_list.js",
6 | "scripts": {
7 | "install": "tsd reinstall",
8 | "build": "tsc -t ES5 -m commonjs src/*.ts tests/*.ts",
9 | "start": "npm run-script build; node src/task_list.js",
10 | "test": "npm run-script build; nodeunit tests"
11 | },
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "readline": "0.0.3"
16 | },
17 | "devDependencies": {
18 | "nodeunit": "^0.9.0",
19 | "tsd": "^0.5.7",
20 | "typescript": "^1.3.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/typescript/README:
--------------------------------------------------------------------------------
1 | Setup
2 | =====
3 |
4 | 1. Install npm: https://www.npmjs.com/
5 |
6 | 2. Install the dependencies:
7 | > npm install
8 |
9 | Usage
10 | =====
11 |
12 | Run the tests:
13 | > npm test
14 |
15 | Run the application:
16 | > npm start
17 |
18 | TODO
19 | ====
20 |
21 | The tests are bit funny at the moment because nodeunit assertions only
22 | fail at the end of the test. Fortunately the built in assertions format nicely
23 | and won't prevent additional tests from running so I use them mostly.
24 | Except one at the end to keep the grunt nodeunit plugin happy.
25 | This could be improved by migrating to a different test framework.
26 | Mocha looks good.
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/java/src/main/java/com/codurance/training/tasks/Task.java:
--------------------------------------------------------------------------------
1 | package com.codurance.training.tasks;
2 |
3 | public final class Task {
4 | private final long id;
5 | private final String description;
6 | private boolean done;
7 |
8 | public Task(long id, String description, boolean done) {
9 | this.id = id;
10 | this.description = description;
11 | this.done = done;
12 | }
13 |
14 | public long getId() {
15 | return id;
16 | }
17 |
18 | public String getDescription() {
19 | return description;
20 | }
21 |
22 | public boolean isDone() {
23 | return done;
24 | }
25 |
26 | public void setDone(boolean done) {
27 | this.done = done;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/csharp/Tasks.Tests/Tasks.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 | all
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/golang/src/task/task.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Task describes an elementary task.
4 | type Task struct {
5 | id int64
6 | description string
7 | done bool
8 | }
9 |
10 | // NewTask initializes a Task with the given ID, description and completion status.
11 | func NewTask(id int64, description string, done bool) *Task {
12 | return &Task{
13 | id: id,
14 | description: description,
15 | done: done,
16 | }
17 | }
18 |
19 | // GetID returns the task ID.
20 | func (t *Task) GetID() int64 {
21 | return t.id
22 | }
23 |
24 | // GetDescription returns the task description.
25 | func (t *Task) GetDescription() string {
26 | return t.description
27 | }
28 |
29 | // IsDone returns whether the task is done or not.
30 | func (t *Task) IsDone() bool {
31 | return t.done
32 | }
33 |
34 | // SetDone changes the completion status of the task.
35 | func (t *Task) SetDone(done bool) {
36 | t.done = done
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Codurance, Ltd.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/golang/README.md:
--------------------------------------------------------------------------------
1 | # Golang code
2 |
3 | ## Usage
4 |
5 | Only a `go` binary is required. Get it through your distribution repositories or on the [official Golang site](https://golang.org/dl/).
6 |
7 | #### Run the tests
8 |
9 | ```sh
10 | > ./run_tests.sh [-v]
11 | ```
12 |
13 | (Calls `go test` after setting up `GOPATH`)
14 |
15 | #### Run the application
16 |
17 | ```sh
18 | > ./build_and_run.sh
19 | ```
20 |
21 | (Calls `go build` after setting up `GOPATH`)
22 |
23 | ## Notes on testing
24 |
25 | The main scenario test in `main_test.go` writes to the input descriptor
26 | of `TaskList.Run()`, and reads from its output descriptor, through
27 | [`io.Pipe`](https://golang.org/pkg/io/#Pipe).
28 |
29 | Writes and reads in pipes are blocking if the other side is not ready,
30 | as a consequence, a missing read (invalid number of lines in the output,
31 | for example) will lead to a deadlock.
32 |
33 | Similarly, the scenario expects the `TaskList.Run()` to finish (reception of the
34 | `quit` command), if it is forgotten another deadlock will be detected.
35 |
36 | In the case of deadlocks, test output will be unusable: to help with debugging,
37 | current scenario steps are logged on standard output and displayed when using
38 | `./run_tests.sh -v`.
39 |
--------------------------------------------------------------------------------
/python/README.md:
--------------------------------------------------------------------------------
1 | Python
2 | =====
3 | Usage
4 | -----
5 | The application requires Python 3.5 or above
6 |
7 | First, create a virtual environment
8 | ```
9 | pip install virtualenv
10 | virtualenv venv
11 | ```
12 |
13 | To activate venv on Windows
14 | ```
15 | venv\Scripts\activate
16 | ```
17 |
18 | To activate venv on Linux
19 | ```
20 | source venv/bin/activate
21 | ```
22 |
23 | To run tests:
24 | ```
25 | python -m unittest -v
26 | ```
27 |
28 | To run the application:
29 | ```
30 | python -m task_list
31 | ```
32 |
33 | Notes on testing
34 | ----------------
35 | For end-to-end testing, a subprocess was used instead of threading. The subprocess module allows
36 | you to create a new process and connect to their input and output pipes.
37 |
38 | The application test runs main from task_list module and then injects the inputs into stdin.
39 | The IO Pipe is blocking which more closely emulates the real behavior of stdin when calling readline.
40 | The call will block until data is written to stdin.
41 | Likewise stdout.read will block until there is data to be read.
42 |
43 | Because of the potential for blocking, a threaded timer was introduced
44 | to kill the subprocess on deadlock. The timeout is currently set to 2 seconds
45 | but if additional tests run longer than that the timeout should be increased.
46 |
47 | As the subprocess captures all input and output, using print statements for debugging during tests
48 | will not work as expected.
--------------------------------------------------------------------------------
/csharp/Tasks.Tests/FakeConsole.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 | using System.IO;
4 | using System.IO.Pipes;
5 | using System.Threading;
6 |
7 | namespace Tasks
8 | {
9 | public class FakeConsole : IConsole
10 | {
11 | private readonly TextReader inputReader;
12 | private readonly TextWriter inputWriter;
13 |
14 | private readonly TextReader outputReader;
15 | private readonly TextWriter outputWriter;
16 |
17 | public FakeConsole()
18 | {
19 | Stream inputStream = new BlockingStream(new ProducerConsumerStream());
20 | this.inputReader = new StreamReader(inputStream);
21 | this.inputWriter = new StreamWriter(inputStream) { AutoFlush = true };
22 |
23 | Stream outputStream = new BlockingStream(new ProducerConsumerStream());
24 | this.outputReader = new StreamReader(outputStream);
25 | this.outputWriter = new StreamWriter(outputStream) { AutoFlush = true };
26 | }
27 |
28 | public string ReadLine()
29 | {
30 | return inputReader.ReadLine();
31 | }
32 |
33 | public void Write(string format, params object[] args)
34 | {
35 | outputWriter.Write(format, args);
36 | }
37 |
38 | public void WriteLine(string format, params object[] args)
39 | {
40 | outputWriter.WriteLine(format, args);
41 | }
42 |
43 | public void WriteLine()
44 | {
45 | outputWriter.WriteLine();
46 | }
47 |
48 | public void SendInput(string input)
49 | {
50 | inputWriter.Write(input);
51 | }
52 |
53 | public string RetrieveOutput(int length)
54 | {
55 | var buffer = new char[length];
56 | outputReader.ReadBlock(buffer, 0, length);
57 | return new string(buffer);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/python/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Python template
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | ../ruby/lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 | .pytest_cache/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 | db.sqlite3
60 |
61 | # Flask stuff:
62 | instance/
63 | .webassets-cache
64 |
65 | # Scrapy stuff:
66 | .scrapy
67 |
68 | # Sphinx documentation
69 | docs/_build/
70 |
71 | # PyBuilder
72 | target/
73 |
74 | # Jupyter Notebook
75 | .ipynb_checkpoints
76 |
77 | # pyenv
78 | .python-version
79 |
80 | # celery beat schedule file
81 | celerybeat-schedule
82 |
83 | # SageMath parsed files
84 | *.sage.py
85 |
86 | # Environments
87 | .env
88 | .venv
89 | env/
90 | venv/
91 | ENV/
92 | env.bak/
93 | venv.bak/
94 |
95 | # Spyder project settings
96 | .spyderproject
97 | .spyproject
98 |
99 | # Rope project settings
100 | .ropeproject
101 |
102 | # mkdocs documentation
103 | /site
104 |
105 | # mypy
106 | .mypy_cache/
107 |
108 | /.idea
--------------------------------------------------------------------------------
/csharp/Tasks.Tests/ProducerConsumerStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Tasks
5 | {
6 | class ProducerConsumerStream : Stream
7 | {
8 | private readonly MemoryStream underlyingStream;
9 | private long readPosition = 0;
10 | private long writePosition = 0;
11 |
12 | public ProducerConsumerStream()
13 | {
14 | this.underlyingStream = new MemoryStream();
15 | }
16 |
17 | public override void Flush()
18 | {
19 | lock (underlyingStream)
20 | {
21 | underlyingStream.Flush();
22 | }
23 | }
24 |
25 | public override int Read(byte[] buffer, int offset, int count)
26 | {
27 | lock (underlyingStream)
28 | {
29 | underlyingStream.Position = readPosition;
30 | int read = underlyingStream.Read(buffer, offset, count);
31 | readPosition = underlyingStream.Position;
32 | return read;
33 | }
34 | }
35 |
36 | public override void Write(byte[] buffer, int offset, int count)
37 | {
38 | lock (underlyingStream)
39 | {
40 | underlyingStream.Position = writePosition;
41 | underlyingStream.Write(buffer, offset, count);
42 | writePosition = underlyingStream.Position;
43 | }
44 | }
45 |
46 | public override bool CanRead { get { return true; } }
47 |
48 | public override bool CanSeek { get { return false; } }
49 |
50 | public override bool CanWrite { get { return true; } }
51 |
52 | public override long Length
53 | {
54 | get
55 | {
56 | lock (underlyingStream)
57 | {
58 | return underlyingStream.Length;
59 | }
60 | }
61 | }
62 |
63 | public override long Position
64 | {
65 | get { throw new NotSupportedException(); }
66 | set { throw new NotSupportedException(); }
67 | }
68 |
69 | public override long Seek(long offset, SeekOrigin origin)
70 | {
71 | throw new NotSupportedException();
72 | }
73 |
74 | public override void SetLength(long value)
75 | {
76 | throw new NotImplementedException();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/java/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | com.codurance.training
5 | tasks
6 | 0.1
7 |
8 | Task List
9 |
10 |
11 |
12 | junit
13 | junit
14 | 4.13.1
15 | test
16 |
17 |
18 | org.hamcrest
19 | java-hamcrest
20 | 2.0.0.0
21 | test
22 |
23 |
24 |
25 |
26 |
27 |
28 | org.apache.maven.plugins
29 | maven-compiler-plugin
30 | 2.5.1
31 |
32 | 1.8
33 | 1.8
34 |
35 |
36 |
37 | org.codehaus.mojo
38 | appassembler-maven-plugin
39 | 1.8
40 |
41 |
42 |
43 | task-list
44 | com.codurance.training.tasks.TaskList
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/csharp/Tasks.Tests/BlockingStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 |
5 | namespace Tasks
6 | {
7 | public class BlockingStream : Stream
8 | {
9 | private readonly Stream underlyingStream;
10 |
11 | public BlockingStream(Stream underlyingStream)
12 | {
13 | this.underlyingStream = underlyingStream;
14 | }
15 |
16 | public override void Flush()
17 | {
18 | lock (underlyingStream)
19 | {
20 | underlyingStream.Flush();
21 | }
22 | }
23 |
24 | public override int Read(byte[] buffer, int offset, int count)
25 | {
26 | int read = 0;
27 | while (true)
28 | {
29 | lock (underlyingStream)
30 | {
31 | read = underlyingStream.Read(buffer, offset, count);
32 | if (read > 0)
33 | {
34 | return read;
35 | }
36 | }
37 |
38 | Thread.Yield();
39 | }
40 | }
41 |
42 | public override long Seek(long offset, SeekOrigin origin)
43 | {
44 | lock (underlyingStream)
45 | {
46 | return underlyingStream.Seek(offset, origin);
47 | }
48 | }
49 |
50 | public override void Write(byte[] buffer, int offset, int count)
51 | {
52 | lock (underlyingStream)
53 | {
54 | underlyingStream.Write(buffer, offset, count);
55 | }
56 | }
57 |
58 | public override void SetLength(long value)
59 | {
60 | underlyingStream.SetLength(value);
61 | }
62 |
63 | public override bool CanRead
64 | {
65 | get
66 | {
67 | return underlyingStream.CanRead;
68 | }
69 | }
70 |
71 | public override bool CanSeek
72 | {
73 | get
74 | {
75 | lock (underlyingStream)
76 | {
77 | return underlyingStream.CanSeek;
78 | }
79 | }
80 | }
81 |
82 | public override bool CanWrite
83 | {
84 | get
85 | {
86 | lock (underlyingStream)
87 | {
88 | return underlyingStream.CanWrite;
89 | }
90 | }
91 | }
92 |
93 | public override long Length
94 | {
95 | get
96 | {
97 | lock (underlyingStream)
98 | {
99 | return underlyingStream.Length;
100 | }
101 | }
102 | }
103 |
104 | public override long Position
105 | {
106 | get
107 | {
108 | lock (underlyingStream)
109 | {
110 | return underlyingStream.Position;
111 | }
112 | }
113 | set
114 | {
115 | lock (underlyingStream)
116 | {
117 | underlyingStream.Position = value;
118 | }
119 | }
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/python/tests/test_application.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import unittest
3 | from threading import Timer
4 |
5 |
6 | class ApplicationTest(unittest.TestCase):
7 | PROMPT = "> "
8 | TIMEOUT = 2
9 |
10 | def setUp(self):
11 | self.proc = subprocess.Popen(
12 | ["python", "-m", "task_list"],
13 | stdin=subprocess.PIPE, stdout=subprocess.PIPE,
14 | universal_newlines=True)
15 | self.timer = Timer(self.TIMEOUT, self.proc.kill)
16 | self.timer.start()
17 |
18 | def tearDown(self):
19 | self.timer.cancel()
20 | self.proc.stdout.close()
21 | self.proc.stdin.close()
22 | while self.proc.returncode is None:
23 | self.proc.poll()
24 |
25 | def test_it_works(self):
26 | self.execute("show")
27 | self.execute("add project secrets")
28 | self.execute("add task secrets Eat more donuts.")
29 | self.execute("add task secrets Destroy all humans.")
30 | self.execute("show")
31 |
32 | self.read_lines(
33 | "secrets",
34 | " [ ] 1: Eat more donuts.",
35 | " [ ] 2: Destroy all humans.",
36 | "")
37 |
38 | self.execute("add project training")
39 | self.execute("add task training Four Elements of Simple Design")
40 | self.execute("add task training SOLID")
41 | self.execute("add task training Coupling and Cohesion")
42 | self.execute("add task training Primitive Obsession")
43 | self.execute("add task training Outside-In TDD")
44 | self.execute("add task training Interaction-Driven Design")
45 |
46 | self.execute("check 1")
47 | self.execute("check 3")
48 | self.execute("check 5")
49 | self.execute("check 6")
50 | self.execute("show")
51 |
52 | self.read_lines(
53 | "secrets",
54 | " [x] 1: Eat more donuts.",
55 | " [ ] 2: Destroy all humans.",
56 | "",
57 | "training",
58 | " [x] 3: Four Elements of Simple Design",
59 | " [ ] 4: SOLID",
60 | " [x] 5: Coupling and Cohesion",
61 | " [x] 6: Primitive Obsession",
62 | " [ ] 7: Outside-In TDD",
63 | " [ ] 8: Interaction-Driven Design",
64 | "")
65 |
66 | self.execute("quit")
67 |
68 | def execute(self, command):
69 | self.write(command + "\n")
70 |
71 | def write(self, command):
72 | self.read(self.PROMPT)
73 | self.proc.stdin.write(command)
74 | self.proc.stdin.flush()
75 |
76 | def read(self, expected_output):
77 | output = self.proc.stdout.read(len(expected_output))
78 | self.assertEqual(expected_output, output)
79 |
80 | def read_lines(self, *lines):
81 | for line in lines:
82 | self.read(line + "\n")
83 |
--------------------------------------------------------------------------------
/csharp/TaskList.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30128.74
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tasks", "Tasks\Tasks.csproj", "{15B0CBFF-BE62-420C-878A-94FC6590A01D}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tasks.Tests", "Tasks.Tests\Tasks.Tests.csproj", "{7300E494-0DC7-4233-8732-93D685B0F485}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|Mixed Platforms = Debug|Mixed Platforms
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|Mixed Platforms = Release|Mixed Platforms
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
23 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
24 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Debug|x86.ActiveCfg = Debug|Any CPU
25 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Debug|x86.Build.0 = Debug|Any CPU
26 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
29 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
30 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Release|x86.ActiveCfg = Release|Any CPU
31 | {15B0CBFF-BE62-420C-878A-94FC6590A01D}.Release|x86.Build.0 = Release|Any CPU
32 | {7300E494-0DC7-4233-8732-93D685B0F485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {7300E494-0DC7-4233-8732-93D685B0F485}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {7300E494-0DC7-4233-8732-93D685B0F485}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
35 | {7300E494-0DC7-4233-8732-93D685B0F485}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
36 | {7300E494-0DC7-4233-8732-93D685B0F485}.Debug|x86.ActiveCfg = Debug|Any CPU
37 | {7300E494-0DC7-4233-8732-93D685B0F485}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {7300E494-0DC7-4233-8732-93D685B0F485}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {7300E494-0DC7-4233-8732-93D685B0F485}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
40 | {7300E494-0DC7-4233-8732-93D685B0F485}.Release|Mixed Platforms.Build.0 = Release|Any CPU
41 | {7300E494-0DC7-4233-8732-93D685B0F485}.Release|x86.ActiveCfg = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {E7DD7DB2-3EAD-49DB-9626-0074036608B6}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/csharp/Tasks.Tests/ApplicationTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using NUnit.Framework;
4 |
5 | namespace Tasks
6 | {
7 | [TestFixture]
8 | public sealed class ApplicationTest
9 | {
10 | public const string PROMPT = "> ";
11 |
12 | private FakeConsole console;
13 | private System.Threading.Thread applicationThread;
14 |
15 | [SetUp]
16 | public void StartTheApplication()
17 | {
18 | this.console = new FakeConsole();
19 | var taskList = new TaskList(console);
20 | this.applicationThread = new System.Threading.Thread(() => taskList.Run());
21 | applicationThread.Start();
22 | }
23 |
24 | [TearDown]
25 | public void KillTheApplication()
26 | {
27 | if (applicationThread == null || !applicationThread.IsAlive)
28 | {
29 | return;
30 | }
31 |
32 | applicationThread.Abort();
33 | throw new Exception("The application is still running.");
34 | }
35 |
36 | [Test, Timeout(1000)]
37 | public void ItWorks()
38 | {
39 | Execute("show");
40 |
41 | Execute("add project secrets");
42 | Execute("add task secrets Eat more donuts.");
43 | Execute("add task secrets Destroy all humans.");
44 |
45 | Execute("show");
46 | ReadLines(
47 | "secrets",
48 | " [ ] 1: Eat more donuts.",
49 | " [ ] 2: Destroy all humans.",
50 | ""
51 | );
52 |
53 | Execute("add project training");
54 | Execute("add task training Four Elements of Simple Design");
55 | Execute("add task training SOLID");
56 | Execute("add task training Coupling and Cohesion");
57 | Execute("add task training Primitive Obsession");
58 | Execute("add task training Outside-In TDD");
59 | Execute("add task training Interaction-Driven Design");
60 |
61 | Execute("check 1");
62 | Execute("check 3");
63 | Execute("check 5");
64 | Execute("check 6");
65 |
66 | Execute("show");
67 | ReadLines(
68 | "secrets",
69 | " [x] 1: Eat more donuts.",
70 | " [ ] 2: Destroy all humans.",
71 | "",
72 | "training",
73 | " [x] 3: Four Elements of Simple Design",
74 | " [ ] 4: SOLID",
75 | " [x] 5: Coupling and Cohesion",
76 | " [x] 6: Primitive Obsession",
77 | " [ ] 7: Outside-In TDD",
78 | " [ ] 8: Interaction-Driven Design",
79 | ""
80 | );
81 |
82 | Execute("quit");
83 | }
84 |
85 | private void Execute(string command)
86 | {
87 | Read(PROMPT);
88 | Write(command);
89 | }
90 |
91 | private void Read(string expectedOutput)
92 | {
93 | var length = expectedOutput.Length;
94 | var actualOutput = console.RetrieveOutput(expectedOutput.Length);
95 | Assert.AreEqual(expectedOutput, actualOutput);
96 | }
97 |
98 | private void ReadLines(params string[] expectedOutput)
99 | {
100 | foreach (var line in expectedOutput)
101 | {
102 | Read(line + Environment.NewLine);
103 | }
104 | }
105 |
106 | private void Write(string input)
107 | {
108 | console.SendInput(input + Environment.NewLine);
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/ruby/spec/application_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rspec'
2 | require 'stringio'
3 | require 'timeout'
4 |
5 | require_relative '../lib/task_list'
6 |
7 | describe 'application' do
8 | PROMPT = '> '
9 |
10 | around :each do |example|
11 | @input_reader, @input_writer = IO.pipe
12 | @output_reader, @output_writer = IO.pipe
13 |
14 | application = TaskList.new(@input_reader, @output_writer)
15 | @application_thread = Thread.new do
16 | application.run
17 | end
18 | @application_thread.abort_on_exception = true
19 |
20 | example.run
21 |
22 | @input_reader.close
23 | @input_writer.close
24 | @output_reader.close
25 | @output_writer.close
26 | end
27 |
28 | after :each do
29 | next unless still_running?
30 | sleep 1
31 | next unless still_running?
32 | @application_thread.kill
33 | raise 'The application is still running.'
34 | end
35 |
36 | it 'works' do
37 | Timeout::timeout 1 do
38 | execute('show')
39 |
40 | execute('add project secrets')
41 | execute('add task secrets Eat more donuts.')
42 | execute('add task secrets Destroy all humans.')
43 |
44 | execute('show')
45 | read_lines(
46 | 'secrets',
47 | ' [ ] 1: Eat more donuts.',
48 | ' [ ] 2: Destroy all humans.',
49 | ''
50 | )
51 |
52 | execute('add project training')
53 | execute('add task training Four Elements of Simple Design')
54 | execute('add task training SOLID')
55 | execute('add task training Coupling and Cohesion')
56 | execute('add task training Primitive Obsession')
57 | execute('add task training Outside-In TDD')
58 | execute('add task training Interaction-Driven Design')
59 |
60 | execute('check 1')
61 | execute('check 3')
62 | execute('check 5')
63 | execute('check 6')
64 |
65 | execute('show')
66 | read_lines(
67 | 'secrets',
68 | ' [x] 1: Eat more donuts.',
69 | ' [ ] 2: Destroy all humans.',
70 | '',
71 | 'training',
72 | ' [x] 3: Four Elements of Simple Design',
73 | ' [ ] 4: SOLID',
74 | ' [x] 5: Coupling and Cohesion',
75 | ' [x] 6: Primitive Obsession',
76 | ' [ ] 7: Outside-In TDD',
77 | ' [ ] 8: Interaction-Driven Design',
78 | ''
79 | )
80 |
81 | execute('quit')
82 | end
83 | end
84 |
85 | def execute(command)
86 | read PROMPT
87 | write command
88 | end
89 |
90 | def read(expected_output)
91 | actual_output = @output_reader.read(expected_output.length)
92 | expect(actual_output).to eq expected_output
93 | end
94 |
95 | def read_lines(*expected_output)
96 | expected_output.each do |line|
97 | read "#{line}\n"
98 | end
99 | end
100 |
101 | def write(input)
102 | @input_writer.puts input
103 | end
104 |
105 | def still_running?
106 | @application_thread && @application_thread.alive?
107 | end
108 | end
109 |
--------------------------------------------------------------------------------
/ruby/lib/task_list.rb:
--------------------------------------------------------------------------------
1 | require_relative 'task'
2 |
3 | class TaskList
4 | QUIT = 'quit'
5 |
6 | def initialize(input, output)
7 | @input = input
8 | @output = output
9 |
10 | @tasks = {}
11 | end
12 |
13 | def run
14 | while true
15 | @output.print('> ')
16 | @output.flush
17 |
18 | command = @input.readline.strip
19 | break if command == QUIT
20 |
21 | execute(command)
22 | end
23 | end
24 |
25 | private
26 |
27 | def execute(command_line)
28 | command, rest = command_line.split(/ /, 2)
29 | case command
30 | when 'show'
31 | show
32 | when 'add'
33 | add rest
34 | when 'check'
35 | check rest
36 | when 'uncheck'
37 | uncheck rest
38 | when 'help'
39 | help
40 | else
41 | error command
42 | end
43 | end
44 |
45 | def show
46 | @tasks.each do |project_name, project_tasks|
47 | @output.puts project_name
48 | project_tasks.each do |task|
49 | @output.printf(" [%c] %d: %s\n", (task.done? ? 'x' : ' '), task.id, task.description)
50 | end
51 | @output.puts
52 | end
53 | end
54 |
55 | def add(command_line)
56 | subcommand, rest = command_line.split(/ /, 2)
57 | if subcommand == 'project'
58 | add_project rest
59 | elsif subcommand == 'task'
60 | project, description = rest.split(/ /, 2)
61 | add_task project, description
62 | end
63 | end
64 |
65 | def add_project(name)
66 | @tasks[name] = []
67 | end
68 |
69 | def add_task(project, description)
70 | project_tasks = @tasks[project]
71 | if project_tasks.nil?
72 | @output.printf("Could not find a project with the name \"%s\".\n", project)
73 | return
74 | end
75 | project_tasks << Task.new(next_id, description, false)
76 | end
77 |
78 | def check(id_string)
79 | set_done(id_string, true)
80 | end
81 |
82 | def uncheck(id_string)
83 | set_done(id_string, false)
84 | end
85 |
86 | def set_done(id_string, done)
87 | id = id_string.to_i
88 | task = @tasks.collect { |project_name, project_tasks|
89 | project_tasks.find { |t| t.id == id }
90 | }.reject(&:nil?).first
91 |
92 | if task.nil?
93 | @output.printf("Could not find a task with an ID of %d.\n", id)
94 | return
95 | end
96 |
97 | task.done = done
98 | end
99 |
100 | def help
101 | @output.puts('Commands:')
102 | @output.puts(' show')
103 | @output.puts(' add project ')
104 | @output.puts(' add task ')
105 | @output.puts(' check ')
106 | @output.puts(' uncheck ')
107 | @output.puts()
108 | end
109 |
110 | def error(command)
111 | @output.printf("I don't know what the command \"%s\" is.\n", command)
112 | end
113 |
114 | def next_id
115 | @last_id ||= 0
116 | @last_id += 1
117 | @last_id
118 | end
119 | end
120 |
121 | if __FILE__ == $0
122 | TaskList.new($stdin, $stdout).run
123 | end
124 |
--------------------------------------------------------------------------------
/scala/src/test/scala/TaskListSpec.scala:
--------------------------------------------------------------------------------
1 | import java.io._
2 |
3 | import org.scalatest.{BeforeAndAfter, FlatSpec}
4 |
5 | class TaskListSpec extends FlatSpec with BeforeAndAfter {
6 |
7 | var applicationThread: Thread = null
8 |
9 | val inStream = new PipedOutputStream()
10 | val inWriter = new PrintWriter(inStream, true)
11 |
12 | val outStream = new PipedInputStream()
13 | val outReader = new BufferedReader(new InputStreamReader(outStream))
14 |
15 | before {
16 | val in = new BufferedReader(new InputStreamReader(new PipedInputStream(inStream)))
17 | val out = new PipedOutputStream(outStream)
18 | scala.Console.withOut(out) {
19 | scala.Console.withIn(in) {
20 | applicationThread = new Thread(new TaskList)
21 | applicationThread.start()
22 | }
23 | }
24 | }
25 |
26 | after {
27 | if (applicationThread != null && applicationThread.isAlive) {
28 | applicationThread.interrupt()
29 | throw new IllegalStateException("The application is still running.")
30 | }
31 | }
32 |
33 | "A task list" should "work" in {
34 |
35 | executeCommand("show")
36 |
37 | executeCommand("add project secrets")
38 | executeCommand("add task secrets Eat more donuts.")
39 | executeCommand("add task secrets Destroy all humans.")
40 |
41 | executeCommand("show")
42 | readLines(
43 | "secrets",
44 | " [ ] 1: Eat more donuts.",
45 | " [ ] 2: Destroy all humans.",
46 | "")
47 |
48 | executeCommand("add project training")
49 | executeCommand("add task training Four Elements of Simple Design")
50 | executeCommand("add task training SOLID")
51 | executeCommand("add task training Coupling and Cohesion")
52 | executeCommand("add task training Primitive Obsession")
53 | executeCommand("add task training Outside-In TDD")
54 | executeCommand("add task training Interaction-Driven Design")
55 |
56 | executeCommand("check 1")
57 | executeCommand("check 3")
58 | executeCommand("check 5")
59 | executeCommand("check 6")
60 |
61 | executeCommand("show")
62 | readLines(
63 | "secrets",
64 | " [x] 1: Eat more donuts.",
65 | " [ ] 2: Destroy all humans.",
66 | "",
67 | "training",
68 | " [x] 3: Four Elements of Simple Design",
69 | " [ ] 4: SOLID",
70 | " [x] 5: Coupling and Cohesion",
71 | " [x] 6: Primitive Obsession",
72 | " [ ] 7: Outside-In TDD",
73 | " [ ] 8: Interaction-Driven Design",
74 | "")
75 |
76 | executeCommand("quit")
77 | }
78 |
79 | def executeCommand(command: String): Unit = {
80 | read("» ")
81 | inWriter.println(command)
82 | }
83 |
84 | def readLines(expectedOutput: String*): Unit = {
85 | expectedOutput.foreach { line =>
86 | read(line + System.lineSeparator())
87 | }
88 | }
89 |
90 | def read(expectedOutput: String): Unit = {
91 | val length = expectedOutput.length()
92 | val buffer = new Array[Char](length)
93 | outReader.read(buffer, 0, length)
94 | assert(String.valueOf(buffer) == expectedOutput)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/scala/src/main/scala/TaskList.scala:
--------------------------------------------------------------------------------
1 | import scala.collection.mutable
2 | import scala.collection.mutable.ArrayBuffer
3 | import scala.io.StdIn
4 |
5 | case class Task(id: Long, description: String, var done: Boolean = false)
6 |
7 | class TaskList extends Runnable {
8 |
9 | private val tasks = mutable.HashMap[String, Seq[Task]]()
10 |
11 | override def run(): Unit = {
12 | var finished = false
13 | while(!finished) {
14 | StdIn.readLine("» ") match {
15 | case "quit" => finished = true
16 | case commandLine => execute(commandLine)
17 | }
18 | }
19 | }
20 |
21 | private def execute(commandLine: String): Unit = {
22 | val commandParts = commandLine.split("""\s""", 2)
23 | val command = commandParts(0)
24 | command match {
25 | case "" => ()
26 | case "help" => help()
27 | case "show" => show()
28 | case "add" => add(commandParts(1))
29 | case "check" => check(commandParts(1))
30 | case "uncheck" => uncheck(commandParts(1))
31 | case _ => showError(command)
32 | }
33 | }
34 |
35 | private def help(): Unit = {
36 | Console.println(
37 | """Commands:
38 | | show
39 | | add project
40 | | add task
41 | | check
42 | | uncheck
43 | """.stripMargin)
44 | }
45 |
46 | private def show(): Unit = {
47 | tasks foreach { case (project, projectTasks) =>
48 | Console.println(project)
49 | projectTasks foreach { task =>
50 | val checkbox = if (task.done) "[x]" else "[ ]"
51 | Console.println(s" $checkbox ${task.id}: ${task.description}")
52 | }
53 | Console.println()
54 | }
55 | }
56 |
57 | private def add(commandLine: String): Unit = {
58 | val commandWords = commandLine.split("""\s""", 2)
59 | val command = commandWords(0)
60 | command match {
61 | case "project" => addProject(commandWords(1))
62 | case "task" => {
63 | val projectTask = commandWords(1).split("""\s""", 2)
64 | addTask(projectTask(0), projectTask(1))
65 | }
66 | case _ => showError(commandLine)
67 | }
68 | }
69 |
70 | private def addProject(name: String): Unit = {
71 | tasks += name -> Seq[Task]()
72 | }
73 |
74 | private def addTask(project: String, description: String): Unit = {
75 | tasks.get(project).map { projectTasks =>
76 | tasks.put(project, projectTasks :+ Task(nextId, description))
77 | }.getOrElse {
78 | Console.println(Console.RED + "Could not find a project with the name " + project + Console.WHITE)
79 | }
80 | }
81 |
82 | private def nextId: Long = {
83 | (0L +: tasks.values.flatten.map(_.id).toList).max + 1L
84 | }
85 |
86 | private def check(idString: String): Unit = setDone(idString, true)
87 |
88 | private def uncheck(idString: String): Unit = setDone(idString, false)
89 |
90 | private def setDone(idString: String, done: Boolean): Unit = {
91 | tasks.values.flatten.find(_.id == idString.toLong).foreach { task =>
92 | task.done = done
93 | }
94 | }
95 |
96 | private def showError(command: String): Unit = {
97 | Console.println(Console.RED + "Unknown command: " + command + Console.WHITE)
98 | }
99 | }
100 |
101 | object TaskList {
102 | def main( args:Array[String] ): Unit = {
103 | (new TaskList).run()
104 | }
105 | }
--------------------------------------------------------------------------------
/python/task_list/app.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List
2 |
3 | from task_list.console import Console
4 | from task_list.task import Task
5 |
6 |
7 | class TaskList:
8 | QUIT = "quit"
9 |
10 | def __init__(self, console: Console) -> None:
11 | self.console = console
12 | self.last_id: int = 0
13 | self.tasks: Dict[str, List[Task]] = dict()
14 |
15 | def run(self) -> None:
16 | while True:
17 | command = self.console.input("> ")
18 | if command == self.QUIT:
19 | break
20 | self.execute(command)
21 |
22 | def execute(self, command_line: str) -> None:
23 | command_rest = command_line.split(" ", 1)
24 | command = command_rest[0]
25 | if command == "show":
26 | self.show()
27 | elif command == "add":
28 | self.add(command_rest[1])
29 | elif command == "check":
30 | self.check(command_rest[1])
31 | elif command == "uncheck":
32 | self.uncheck(command_rest[1])
33 | elif command == "help":
34 | self.help()
35 | else:
36 | self.error(command)
37 |
38 | def show(self) -> None:
39 | for project, tasks in self.tasks.items():
40 | self.console.print(project)
41 | for task in tasks:
42 | self.console.print(f" [{'x' if task.is_done() else ' '}] {task.id}: {task.description}")
43 | self.console.print()
44 |
45 | def add(self, command_line: str) -> None:
46 | sub_command_rest = command_line.split(" ", 1)
47 | sub_command = sub_command_rest[0]
48 | if sub_command == "project":
49 | self.add_project(sub_command_rest[1])
50 | elif sub_command == "task":
51 | project_task = sub_command_rest[1].split(" ", 1)
52 | self.add_task(project_task[0], project_task[1])
53 |
54 | def add_project(self, name: str) -> None:
55 | self.tasks[name] = []
56 |
57 | def add_task(self, project: str, description: str) -> None:
58 | project_tasks = self.tasks.get(project)
59 | if project_tasks is None:
60 | self.console.print(f"Could not find a project with the name {project}.")
61 | self.console.print()
62 | return
63 | project_tasks.append(Task(self.next_id(), description, False))
64 |
65 | def check(self, id_string: str) -> None:
66 | self.set_done(id_string, True)
67 |
68 | def uncheck(self, id_string: str) -> None:
69 | self.set_done(id_string, False)
70 |
71 | def set_done(self, id_string: str, done: bool) -> None:
72 | id_ = int(id_string)
73 | for project, tasks in self.tasks.items():
74 | for task in tasks:
75 | if task.id == id_:
76 | task.set_done(done)
77 | return
78 | self.console.print(f"Could not find a task with an ID of {id_}")
79 | self.console.print()
80 |
81 | def help(self) -> None:
82 | self.console.print("Commands:")
83 | self.console.print(" show")
84 | self.console.print(" add project ")
85 | self.console.print(" add task ")
86 | self.console.print(" check ")
87 | self.console.print(" uncheck ")
88 | self.console.print()
89 |
90 | def error(self, command: str) -> None:
91 | self.console.print(f"I don't know what the command {command} is.")
92 | self.console.print()
93 |
94 | def next_id(self) -> int:
95 | self.last_id += 1
96 | return self.last_id
97 |
98 |
--------------------------------------------------------------------------------
/csharp/Tasks/TaskList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Tasks
6 | {
7 | public sealed class TaskList
8 | {
9 | private const string QUIT = "quit";
10 |
11 | private readonly IDictionary> tasks = new Dictionary>();
12 | private readonly IConsole console;
13 |
14 | private long lastId = 0;
15 |
16 | public static void Main(string[] args)
17 | {
18 | new TaskList(new RealConsole()).Run();
19 | }
20 |
21 | public TaskList(IConsole console)
22 | {
23 | this.console = console;
24 | }
25 |
26 | public void Run()
27 | {
28 | while (true) {
29 | console.Write("> ");
30 | var command = console.ReadLine();
31 | if (command == QUIT) {
32 | break;
33 | }
34 | Execute(command);
35 | }
36 | }
37 |
38 | private void Execute(string commandLine)
39 | {
40 | var commandRest = commandLine.Split(" ".ToCharArray(), 2);
41 | var command = commandRest[0];
42 | switch (command) {
43 | case "show":
44 | Show();
45 | break;
46 | case "add":
47 | Add(commandRest[1]);
48 | break;
49 | case "check":
50 | Check(commandRest[1]);
51 | break;
52 | case "uncheck":
53 | Uncheck(commandRest[1]);
54 | break;
55 | case "help":
56 | Help();
57 | break;
58 | default:
59 | Error(command);
60 | break;
61 | }
62 | }
63 |
64 | private void Show()
65 | {
66 | foreach (var project in tasks) {
67 | console.WriteLine(project.Key);
68 | foreach (var task in project.Value) {
69 | console.WriteLine(" [{0}] {1}: {2}", (task.Done ? 'x' : ' '), task.Id, task.Description);
70 | }
71 | console.WriteLine();
72 | }
73 | }
74 |
75 | private void Add(string commandLine)
76 | {
77 | var subcommandRest = commandLine.Split(" ".ToCharArray(), 2);
78 | var subcommand = subcommandRest[0];
79 | if (subcommand == "project") {
80 | AddProject(subcommandRest[1]);
81 | } else if (subcommand == "task") {
82 | var projectTask = subcommandRest[1].Split(" ".ToCharArray(), 2);
83 | AddTask(projectTask[0], projectTask[1]);
84 | }
85 | }
86 |
87 | private void AddProject(string name)
88 | {
89 | tasks[name] = new List();
90 | }
91 |
92 | private void AddTask(string project, string description)
93 | {
94 | if (!tasks.TryGetValue(project, out IList projectTasks))
95 | {
96 | Console.WriteLine("Could not find a project with the name \"{0}\".", project);
97 | return;
98 | }
99 | projectTasks.Add(new Task { Id = NextId(), Description = description, Done = false });
100 | }
101 |
102 | private void Check(string idString)
103 | {
104 | SetDone(idString, true);
105 | }
106 |
107 | private void Uncheck(string idString)
108 | {
109 | SetDone(idString, false);
110 | }
111 |
112 | private void SetDone(string idString, bool done)
113 | {
114 | int id = int.Parse(idString);
115 | var identifiedTask = tasks
116 | .Select(project => project.Value.FirstOrDefault(task => task.Id == id))
117 | .Where(task => task != null)
118 | .FirstOrDefault();
119 | if (identifiedTask == null) {
120 | console.WriteLine("Could not find a task with an ID of {0}.", id);
121 | return;
122 | }
123 |
124 | identifiedTask.Done = done;
125 | }
126 |
127 | private void Help()
128 | {
129 | console.WriteLine("Commands:");
130 | console.WriteLine(" show");
131 | console.WriteLine(" add project ");
132 | console.WriteLine(" add task ");
133 | console.WriteLine(" check ");
134 | console.WriteLine(" uncheck ");
135 | console.WriteLine();
136 | }
137 |
138 | private void Error(string command)
139 | {
140 | console.WriteLine("I don't know what the command \"{0}\" is.", command);
141 | }
142 |
143 | private long NextId()
144 | {
145 | return ++lastId;
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/kotlin/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | com.codurance.training
5 | tasks
6 | 0.1
7 |
8 | Task List
9 |
10 |
11 | 1.6.0
12 |
13 |
14 |
15 |
16 | junit
17 | junit
18 | 4.13.1
19 | test
20 |
21 |
22 | org.hamcrest
23 | java-hamcrest
24 | 2.0.0.0
25 | test
26 |
27 |
28 | org.jetbrains.kotlin
29 | kotlin-stdlib
30 | ${kotlin.version}
31 |
32 |
33 | org.jetbrains.kotlin
34 | kotlin-test
35 | ${kotlin.version}
36 | test
37 |
38 |
39 |
40 |
41 |
42 |
43 | org.codehaus.mojo
44 | appassembler-maven-plugin
45 | 1.8
46 |
47 |
48 |
49 | task-list
50 | com.codurance.training.tasks.TaskList
51 |
52 |
53 |
54 |
55 |
56 | org.jetbrains.kotlin
57 | kotlin-maven-plugin
58 | ${kotlin.version}
59 |
60 |
61 | compile
62 | compile
63 |
64 | compile
65 |
66 |
67 |
68 | test-compile
69 | test-compile
70 |
71 | test-compile
72 |
73 |
74 |
75 |
76 |
77 | org.apache.maven.plugins
78 | maven-compiler-plugin
79 | 2.5.1
80 |
81 |
82 | compile
83 | compile
84 |
85 | compile
86 |
87 |
88 |
89 | testCompile
90 | test-compile
91 |
92 | testCompile
93 |
94 |
95 |
96 |
97 | 1.8
98 | 1.8
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/golang/src/task/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | "sync"
8 | "testing"
9 | )
10 |
11 | type scenarioTester struct {
12 | *testing.T
13 |
14 | inWriter io.Writer
15 | outReader io.Reader
16 | outScanner *bufio.Scanner
17 | }
18 |
19 | func TestRun(t *testing.T) {
20 | // setup input/output
21 | inPR, inPW := io.Pipe()
22 | defer inPR.Close()
23 | outPR, outPW := io.Pipe()
24 | defer outPR.Close()
25 | tester := &scenarioTester{
26 | T: t,
27 | inWriter: inPW,
28 | outReader: outPR,
29 | outScanner: bufio.NewScanner(outPR),
30 | }
31 |
32 | // run main program
33 | var wg sync.WaitGroup
34 | go func() {
35 | wg.Add(1)
36 | NewTaskList(inPR, outPW).Run()
37 | outPW.Close()
38 | wg.Done()
39 | }()
40 |
41 | // run command-line scenario
42 | fmt.Println("(show empty)")
43 | tester.execute("show")
44 |
45 | fmt.Println("(add project)")
46 | tester.execute("add project secrets")
47 | fmt.Println("(add tasks)")
48 | tester.execute("add task secrets Eat more donuts.")
49 | tester.execute("add task secrets Destroy all humans.")
50 |
51 | fmt.Println("(show tasks)")
52 | tester.execute("show")
53 | tester.readLines([]string{
54 | "secrets",
55 | " [ ] 1: Eat more donuts.",
56 | " [ ] 2: Destroy all humans.",
57 | "",
58 | })
59 |
60 | fmt.Println("(add second project)")
61 | tester.execute("add project training")
62 | fmt.Println("(add more tasks)")
63 | tester.execute("add task training Four Elements of Simple Design")
64 | tester.execute("add task training SOLID")
65 | tester.execute("add task training Coupling and Cohesion")
66 | tester.execute("add task training Primitive Obsession")
67 | tester.execute("add task training Outside-In TDD")
68 | tester.execute("add task training Interaction-Driven Design")
69 |
70 | fmt.Println("(check tasks)")
71 | tester.execute("check 1")
72 | tester.execute("check 3")
73 | tester.execute("check 5")
74 | tester.execute("check 6")
75 |
76 | fmt.Println("(show completed tasks)")
77 | tester.execute("show")
78 | tester.readLines([]string{
79 | "secrets",
80 | " [X] 1: Eat more donuts.",
81 | " [ ] 2: Destroy all humans.",
82 | "",
83 | "training",
84 | " [X] 3: Four Elements of Simple Design",
85 | " [ ] 4: SOLID",
86 | " [X] 5: Coupling and Cohesion",
87 | " [X] 6: Primitive Obsession",
88 | " [ ] 7: Outside-In TDD",
89 | " [ ] 8: Interaction-Driven Design",
90 | "",
91 | })
92 |
93 | fmt.Println("(quit)")
94 | tester.execute("quit")
95 |
96 | // make sure main program has quit
97 | inPW.Close()
98 | wg.Wait()
99 | }
100 |
101 | // execute calls a command, by writing it into the scenario writer.
102 | // It first reads the command prompt, then sends the command.
103 | func (t *scenarioTester) execute(cmd string) {
104 | p := make([]byte, len(prompt))
105 | _, err := t.outReader.Read(p)
106 | if err != nil {
107 | t.Errorf("Prompt could not be read: %v", err)
108 | return
109 | }
110 | if string(p) != prompt {
111 | t.Errorf("Invalid prompt, expected \"%s\", got \"%s\"", prompt, string(p))
112 | return
113 | }
114 | // send command
115 | fmt.Fprintln(t.inWriter, cmd)
116 | }
117 |
118 | // readLines reads lines from the scenario scanner, making sure they match
119 | // the expected given lines.
120 | // In case it fails or does not match, makes the calling test fail.
121 | func (t *scenarioTester) readLines(lines []string) {
122 | for _, expected := range lines {
123 | if !t.outScanner.Scan() {
124 | t.Errorf("Expected \"%s\", no input found", expected)
125 | break
126 | }
127 | actual := t.outScanner.Text()
128 | if actual != expected {
129 | t.Errorf("Expected \"%s\", got \"%s\"", expected, actual)
130 | }
131 | }
132 | if err := t.outScanner.Err(); err != nil {
133 | t.Fatalf("Could not read input: %v", err)
134 | }
135 | }
136 |
137 | // discardLines reads lines from the scenario scanner, and drops them.
138 | // Used to empty buffers.
139 | func (t *scenarioTester) discardLines(n int) {
140 | for i := 0; i < n; i++ {
141 | if !t.outScanner.Scan() {
142 | t.Error("Expected a line, no input found")
143 | break
144 | }
145 | }
146 | if err := t.outScanner.Err(); err != nil {
147 | t.Fatalf("Could not read input: %v", err)
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/typescript/tests/application_test.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import assert = require('assert');
4 | import nodeunit = require('nodeunit');
5 | import stream = require('stream');
6 | import task_list = require('../src/task_list');
7 |
8 | class Expectation {
9 | constructor(public ctxt: TestCtxt) {
10 | this.ctxt.expectations.push(this)
11 | }
12 |
13 | test(): boolean {
14 | this.ctxt.test.ok(false, "Override me");
15 | return true;
16 | }
17 | }
18 |
19 | class TestCtxt
20 | {
21 | input = new stream.PassThrough();
22 | output = new stream.PassThrough();
23 | expectations : Expectation[] = [];
24 | tl = new task_list.TaskList(this.input, this.output);
25 |
26 | constructor(public test: nodeunit.Test){
27 | var count = 0;
28 | this.output.on('readable', () => {
29 | if(count > this.expectations.length) {
30 | this.test.ok(false, "Got more output than expected proabably didn't quit")
31 | this.test.done();
32 | } else if(count == this.expectations.length) {
33 | this.test.ok(true);
34 | this.test.done();
35 | } else if(this.expectations[count].test()) {
36 | count += 1;
37 | }
38 | });
39 | this.output.on('end', () => {
40 | this.test.equal(count, this.expectations.length);
41 | this.test.done();
42 | });
43 | }
44 |
45 | read(expected) {
46 | var data = this.output.read(expected.length);
47 | if (data != null) {
48 | data = data.toString();
49 | assert.equal(data, expected);
50 | return true;
51 | }
52 | return false;
53 | }
54 |
55 | run() {
56 | this.test.expect(1);
57 | this.tl.run();
58 | }
59 | }
60 |
61 | class ExecuteExpectation extends Expectation {
62 | prompt = '> ';
63 |
64 | constructor(ctxt: TestCtxt, public cmd:string) {
65 | super(ctxt)
66 | }
67 |
68 | test() {
69 | if (!this.ctxt.read(this.prompt))
70 | return false;
71 | this.ctxt.input.write(this.cmd + '\n');
72 | return true;
73 | }
74 | }
75 |
76 | class OutputExpectation extends Expectation {
77 |
78 | constructor(ctxt: TestCtxt, public out:string) {
79 | super(ctxt)
80 | }
81 |
82 | test() {
83 | return this.ctxt.read(this.out)
84 | }
85 | }
86 |
87 | export function application_test(test: nodeunit.Test) {
88 | var ctxt = new TestCtxt(test);
89 |
90 | function execute(cmd: string) {
91 | new ExecuteExpectation(ctxt, cmd);
92 | }
93 |
94 | function readLines(...strings: string[]) {
95 | strings.forEach((s) => {
96 | new OutputExpectation(ctxt, s + '\n');
97 | })
98 | }
99 |
100 | execute('show');
101 |
102 | execute("add project secrets");
103 | execute("add task secrets Eat more donuts.");
104 | execute("add task secrets Destroy all humans.");
105 |
106 | execute("show");
107 | readLines(
108 | "secrets",
109 | " [ ] 1: Eat more donuts.",
110 | " [ ] 2: Destroy all humans.",
111 | ""
112 | );
113 |
114 | execute("add project training");
115 | execute("add task training Four Elements of Simple Design");
116 | execute("add task training SOLID");
117 | execute("add task training Coupling and Cohesion");
118 | execute("add task training Primitive Obsession");
119 | execute("add task training Outside-In TDD");
120 | execute("add task training Interaction-Driven Design");
121 |
122 | execute("check 1");
123 | execute("check 3");
124 | execute("check 5");
125 | execute("check 6");
126 |
127 | execute("show");
128 | readLines(
129 | "secrets",
130 | " [x] 1: Eat more donuts.",
131 | " [ ] 2: Destroy all humans.",
132 | "",
133 | "training",
134 | " [x] 3: Four Elements of Simple Design",
135 | " [ ] 4: SOLID",
136 | " [x] 5: Coupling and Cohesion",
137 | " [x] 6: Primitive Obsession",
138 | " [ ] 7: Outside-In TDD",
139 | " [ ] 8: Interaction-Driven Design",
140 | ""
141 | );
142 | execute('quit');
143 |
144 | ctxt.run();
145 | }
146 |
--------------------------------------------------------------------------------
/kotlin/src/main/java/com/codurance/training/tasks/TaskList.kt:
--------------------------------------------------------------------------------
1 | package com.codurance.training.tasks
2 |
3 | import java.io.BufferedReader
4 | import java.io.IOException
5 | import java.io.InputStreamReader
6 | import java.io.PrintWriter
7 | import java.util.*
8 |
9 | class TaskList(private val `in`: BufferedReader, private val out: PrintWriter) : Runnable {
10 |
11 | private val tasks = LinkedHashMap>()
12 |
13 | private var lastId: Long = 0
14 |
15 | override fun run() {
16 | while (true) {
17 | out.print("> ")
18 | out.flush()
19 | val command: String
20 | try {
21 | command = `in`.readLine()
22 | } catch (e: IOException) {
23 | throw RuntimeException(e)
24 | }
25 |
26 | if (command == QUIT) {
27 | break
28 | }
29 | execute(command)
30 | }
31 | }
32 |
33 | private fun execute(commandLine: String) {
34 | val commandRest = commandLine.split(" ".toRegex(), 2).toTypedArray()
35 | val command = commandRest[0]
36 | when (command) {
37 | "show" -> show()
38 | "add" -> add(commandRest[1])
39 | "check" -> check(commandRest[1])
40 | "uncheck" -> uncheck(commandRest[1])
41 | "help" -> help()
42 | else -> error(command)
43 | }
44 | }
45 |
46 | private fun show() {
47 | for ((key, value) in tasks) {
48 | out.println(key)
49 | for (task in value) {
50 | out.printf(" [%c] %d: %s%n", if (task.isDone) 'x' else ' ', task.id, task.description)
51 | }
52 | out.println()
53 | }
54 | }
55 |
56 | private fun add(commandLine: String) {
57 | val subcommandRest = commandLine.split(" ".toRegex(), 2).toTypedArray()
58 | val subcommand = subcommandRest[0]
59 | if (subcommand == "project") {
60 | addProject(subcommandRest[1])
61 | } else if (subcommand == "task") {
62 | val projectTask = subcommandRest[1].split(" ".toRegex(), 2).toTypedArray()
63 | addTask(projectTask[0], projectTask[1])
64 | }
65 | }
66 |
67 | private fun addProject(name: String) {
68 | tasks[name] = ArrayList()
69 | }
70 |
71 | private fun addTask(project: String, description: String) {
72 | val projectTasks = tasks[project]
73 | if (projectTasks == null) {
74 | out.printf("Could not find a project with the name \"%s\".", project)
75 | out.println()
76 | return
77 | }
78 | projectTasks.add(Task(nextId(), description, false))
79 | }
80 |
81 | private fun check(idString: String) {
82 | setDone(idString, true)
83 | }
84 |
85 | private fun uncheck(idString: String) {
86 | setDone(idString, false)
87 | }
88 |
89 | private fun setDone(idString: String, done: Boolean) {
90 | val id = Integer.parseInt(idString)
91 | for ((_, value) in tasks) {
92 | for (task in value) {
93 | if (task.id == id.toLong()) {
94 | task.isDone = done
95 | return
96 | }
97 | }
98 | }
99 | out.printf("Could not find a task with an ID of %d.", id)
100 | out.println()
101 | }
102 |
103 | private fun help() {
104 | out.println("Commands:")
105 | out.println(" show")
106 | out.println(" add project ")
107 | out.println(" add task ")
108 | out.println(" check ")
109 | out.println(" uncheck ")
110 | out.println()
111 | }
112 |
113 | private fun error(command: String) {
114 | out.printf("I don't know what the command \"%s\" is.", command)
115 | out.println()
116 | }
117 |
118 | private fun nextId(): Long {
119 | return ++lastId
120 | }
121 |
122 | companion object {
123 | private val QUIT = "quit"
124 |
125 | @Throws(Exception::class)
126 | @JvmStatic
127 | fun main(args: Array) {
128 | val `in` = BufferedReader(InputStreamReader(System.`in`))
129 | val out = PrintWriter(System.out)
130 | TaskList(`in`, out).run()
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/java/src/test/java/com/codurance/training/tasks/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.codurance.training.tasks;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStreamReader;
6 | import java.io.PipedInputStream;
7 | import java.io.PipedOutputStream;
8 | import java.io.PrintWriter;
9 | import org.junit.After;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 |
13 | import static java.lang.System.lineSeparator;
14 | import static org.hamcrest.MatcherAssert.assertThat;
15 | import static org.hamcrest.Matchers.is;
16 |
17 | public final class ApplicationTest {
18 | public static final String PROMPT = "> ";
19 | private final PipedOutputStream inStream = new PipedOutputStream();
20 | private final PrintWriter inWriter = new PrintWriter(inStream, true);
21 |
22 | private final PipedInputStream outStream = new PipedInputStream();
23 | private final BufferedReader outReader = new BufferedReader(new InputStreamReader(outStream));
24 |
25 | private Thread applicationThread;
26 |
27 | public ApplicationTest() throws IOException {
28 | BufferedReader in = new BufferedReader(new InputStreamReader(new PipedInputStream(inStream)));
29 | PrintWriter out = new PrintWriter(new PipedOutputStream(outStream), true);
30 | TaskList taskList = new TaskList(in, out);
31 | applicationThread = new Thread(taskList);
32 | }
33 |
34 | @Before public void
35 | start_the_application() {
36 | applicationThread.start();
37 | }
38 |
39 | @After public void
40 | kill_the_application() throws IOException, InterruptedException {
41 | if (!stillRunning()) {
42 | return;
43 | }
44 |
45 | Thread.sleep(1000);
46 | if (!stillRunning()) {
47 | return;
48 | }
49 |
50 | applicationThread.interrupt();
51 | throw new IllegalStateException("The application is still running.");
52 | }
53 |
54 | @Test(timeout = 1000) public void
55 | it_works() throws IOException {
56 | execute("show");
57 |
58 | execute("add project secrets");
59 | execute("add task secrets Eat more donuts.");
60 | execute("add task secrets Destroy all humans.");
61 |
62 | execute("show");
63 | readLines(
64 | "secrets",
65 | " [ ] 1: Eat more donuts.",
66 | " [ ] 2: Destroy all humans.",
67 | ""
68 | );
69 |
70 | execute("add project training");
71 | execute("add task training Four Elements of Simple Design");
72 | execute("add task training SOLID");
73 | execute("add task training Coupling and Cohesion");
74 | execute("add task training Primitive Obsession");
75 | execute("add task training Outside-In TDD");
76 | execute("add task training Interaction-Driven Design");
77 |
78 | execute("check 1");
79 | execute("check 3");
80 | execute("check 5");
81 | execute("check 6");
82 |
83 | execute("show");
84 | readLines(
85 | "secrets",
86 | " [x] 1: Eat more donuts.",
87 | " [ ] 2: Destroy all humans.",
88 | "",
89 | "training",
90 | " [x] 3: Four Elements of Simple Design",
91 | " [ ] 4: SOLID",
92 | " [x] 5: Coupling and Cohesion",
93 | " [x] 6: Primitive Obsession",
94 | " [ ] 7: Outside-In TDD",
95 | " [ ] 8: Interaction-Driven Design",
96 | ""
97 | );
98 |
99 | execute("quit");
100 | }
101 |
102 | private void execute(String command) throws IOException {
103 | read(PROMPT);
104 | write(command);
105 | }
106 |
107 | private void read(String expectedOutput) throws IOException {
108 | int length = expectedOutput.length();
109 | char[] buffer = new char[length];
110 | outReader.read(buffer, 0, length);
111 | assertThat(String.valueOf(buffer), is(expectedOutput));
112 | }
113 |
114 | private void readLines(String... expectedOutput) throws IOException {
115 | for (String line : expectedOutput) {
116 | read(line + lineSeparator());
117 | }
118 | }
119 |
120 | private void write(String input) {
121 | inWriter.println(input);
122 | }
123 |
124 | private boolean stillRunning() {
125 | return applicationThread != null && applicationThread.isAlive();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/kotlin/src/test/java/com/codurance/training/tasks/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.codurance.training.tasks;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStreamReader;
6 | import java.io.PipedInputStream;
7 | import java.io.PipedOutputStream;
8 | import java.io.PrintWriter;
9 | import org.junit.After;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 |
13 | import static java.lang.System.lineSeparator;
14 | import static org.hamcrest.MatcherAssert.assertThat;
15 | import static org.hamcrest.Matchers.is;
16 |
17 | public final class ApplicationTest {
18 | public static final String PROMPT = "> ";
19 | private final PipedOutputStream inStream = new PipedOutputStream();
20 | private final PrintWriter inWriter = new PrintWriter(inStream, true);
21 |
22 | private final PipedInputStream outStream = new PipedInputStream();
23 | private final BufferedReader outReader = new BufferedReader(new InputStreamReader(outStream));
24 |
25 | private Thread applicationThread;
26 |
27 | public ApplicationTest() throws IOException {
28 | BufferedReader in = new BufferedReader(new InputStreamReader(new PipedInputStream(inStream)));
29 | PrintWriter out = new PrintWriter(new PipedOutputStream(outStream), true);
30 | TaskList taskList = new TaskList(in, out);
31 | applicationThread = new Thread(taskList);
32 | }
33 |
34 | @Before public void
35 | start_the_application() {
36 | applicationThread.start();
37 | }
38 |
39 | @After public void
40 | kill_the_application() throws IOException, InterruptedException {
41 | if (!stillRunning()) {
42 | return;
43 | }
44 |
45 | Thread.sleep(1000);
46 | if (!stillRunning()) {
47 | return;
48 | }
49 |
50 | applicationThread.interrupt();
51 | throw new IllegalStateException("The application is still running.");
52 | }
53 |
54 | @Test(timeout = 1000) public void
55 | it_works() throws IOException {
56 | execute("show");
57 |
58 | execute("add project secrets");
59 | execute("add task secrets Eat more donuts.");
60 | execute("add task secrets Destroy all humans.");
61 |
62 | execute("show");
63 | readLines(
64 | "secrets",
65 | " [ ] 1: Eat more donuts.",
66 | " [ ] 2: Destroy all humans.",
67 | ""
68 | );
69 |
70 | execute("add project training");
71 | execute("add task training Four Elements of Simple Design");
72 | execute("add task training SOLID");
73 | execute("add task training Coupling and Cohesion");
74 | execute("add task training Primitive Obsession");
75 | execute("add task training Outside-In TDD");
76 | execute("add task training Interaction-Driven Design");
77 |
78 | execute("check 1");
79 | execute("check 3");
80 | execute("check 5");
81 | execute("check 6");
82 |
83 | execute("show");
84 | readLines(
85 | "secrets",
86 | " [x] 1: Eat more donuts.",
87 | " [ ] 2: Destroy all humans.",
88 | "",
89 | "training",
90 | " [x] 3: Four Elements of Simple Design",
91 | " [ ] 4: SOLID",
92 | " [x] 5: Coupling and Cohesion",
93 | " [x] 6: Primitive Obsession",
94 | " [ ] 7: Outside-In TDD",
95 | " [ ] 8: Interaction-Driven Design",
96 | ""
97 | );
98 |
99 | execute("quit");
100 | }
101 |
102 | private void execute(String command) throws IOException {
103 | read(PROMPT);
104 | write(command);
105 | }
106 |
107 | private void read(String expectedOutput) throws IOException {
108 | int length = expectedOutput.length();
109 | char[] buffer = new char[length];
110 | outReader.read(buffer, 0, length);
111 | assertThat(String.valueOf(buffer), is(expectedOutput));
112 | }
113 |
114 | private void readLines(String... expectedOutput) throws IOException {
115 | for (String line : expectedOutput) {
116 | read(line + lineSeparator());
117 | }
118 | }
119 |
120 | private void write(String input) {
121 | inWriter.println(input);
122 | }
123 |
124 | private boolean stillRunning() {
125 | return applicationThread != null && applicationThread.isAlive();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Task List [](https://travis-ci.org/codurance/task-list)
2 |
3 | This is an example of code obsessed with primitives.
4 |
5 | A *primitive* is any concept technical in nature, and not relevant to your business domain. This includes integers, characters, strings, and collections (lists, sets, maps, etc.), but also things like threads, readers, writers, parsers, exceptions, and anything else purely focused on technical concerns. By contrast, the business concepts in this project, "task", "project", etc. should be considered part of your *domain model*. The domain model is the language of the business in which you operate, and using it in your code base helps you avoid speaking different languages, helping you to avoid misunderstandings. In our experience, misunderstandings are the biggest cause of bugs.
6 |
7 | ## Exercise
8 |
9 | Try implementing the following features, refactoring primitives away as you go. Try not to implement any new behaviour until the code you're about to change has been completely refactored to remove primitives, i.e. **_Only refactor the code you're about to change, then make your change. Don't refactor unrelated code._**
10 |
11 | One set of criteria to identify when primitives have been removed is to only allow primitives in constructor parameter lists, and as local variables and private fields. They shouldn't be passed into methods or returned from methods. The only exception is true infrastructure code—code that communicates with the terminal, the network, the database, etc. Infrastructure requires serialisation to primitives, but should be treated as a special case. You could even consider your infrastructure as a separate domain, technical in nature, in which primitives *are* the domain.
12 |
13 | You should try to wrap tests around the behaviour you're refactoring. At the beginning, these will mostly be high-level system tests, but you should find yourself writing more unit tests as you proceed.
14 |
15 | ### Features
16 |
17 | 1. Deadlines
18 | 1. Give each task an optional deadline with the `deadline ` command.
19 | 2. Show all tasks due today with the `today` command.
20 | 2. Customisable IDs
21 | 1. Allow the user to specify an identifier that's not a number.
22 | 2. Disallow spaces and special characters from the ID.
23 | 3. Deletion
24 | 1. Allow users to delete tasks with the `delete ` command.
25 | 4. Views
26 | 1. View tasks by date with the `view by date` command.
27 | 2. View tasks by deadline with the `view by deadline` command.
28 | 3. Don't remove the functionality that allows users to view tasks by project, but change the command to `view by project`.
29 |
30 | ### Considerations and Approaches
31 |
32 | Think about *behaviour attraction*. Quite often, you can reduce the amount of behaviour that relies upon primitives from the outside world (as opposed to internal primitives stored as private fields or locals) simply by moving the behaviour to a *value object* which holds the primitives. If you don't have a value object, create one. These value objects are known as *behaviour attractors* because once they're created, they make it far more obvious where behaviour should live.
33 |
34 | A related principle is to consider the type of object you've created. Is it a true value object (or *record*), which simply consists of `getFoo` methods that return their internal primitives (to be used only with infrastructure, of course), or is it an object with behaviour? If it's the latter, you should avoid exposing any internal state at all. The former should not contain any behaviour. Treating something as both a record and an object generally leads to disaster.
35 |
36 | Your approach will depend on whether you learn toward a functional or an object-oriented style for modelling your domain. Both encourage encapsulation, but *information hiding* techniques are generally only used in object-oriented code. They also differ in the approach used to extract behaviour; functional programming often works with closed sets of behaviour through *tagged unions*, whereas in object-oriented code, we use *polymorphism* to achieve the same ends in an open, extensible manner.
37 |
38 | Separate your commands and queries. Tell an object to do something, or ask it about something, but don't do both.
39 |
40 | Lastly, consider SOLID principles when refactoring:
41 |
42 | * Aim to break large chunks of behaviour into small ones, each with a single responsibility.
43 | * Think about the dimensions in which it should be easy to extend the application.
44 | * Don't surprise your callers. Conform to the interface.
45 | * Segregate behaviour based upon the needs.
46 | * Depend upon abstractions.
47 |
--------------------------------------------------------------------------------
/java/src/main/java/com/codurance/training/tasks/TaskList.java:
--------------------------------------------------------------------------------
1 | package com.codurance.training.tasks;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStreamReader;
6 | import java.io.PrintWriter;
7 | import java.util.ArrayList;
8 | import java.util.LinkedHashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | public final class TaskList implements Runnable {
13 | private static final String QUIT = "quit";
14 |
15 | private final Map> tasks = new LinkedHashMap<>();
16 | private final BufferedReader in;
17 | private final PrintWriter out;
18 |
19 | private long lastId = 0;
20 |
21 | public static void main(String[] args) throws Exception {
22 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
23 | PrintWriter out = new PrintWriter(System.out);
24 | new TaskList(in, out).run();
25 | }
26 |
27 | public TaskList(BufferedReader reader, PrintWriter writer) {
28 | this.in = reader;
29 | this.out = writer;
30 | }
31 |
32 | public void run() {
33 | while (true) {
34 | out.print("> ");
35 | out.flush();
36 | String command;
37 | try {
38 | command = in.readLine();
39 | } catch (IOException e) {
40 | throw new RuntimeException(e);
41 | }
42 | if (command.equals(QUIT)) {
43 | break;
44 | }
45 | execute(command);
46 | }
47 | }
48 |
49 | private void execute(String commandLine) {
50 | String[] commandRest = commandLine.split(" ", 2);
51 | String command = commandRest[0];
52 | switch (command) {
53 | case "show":
54 | show();
55 | break;
56 | case "add":
57 | add(commandRest[1]);
58 | break;
59 | case "check":
60 | check(commandRest[1]);
61 | break;
62 | case "uncheck":
63 | uncheck(commandRest[1]);
64 | break;
65 | case "help":
66 | help();
67 | break;
68 | default:
69 | error(command);
70 | break;
71 | }
72 | }
73 |
74 | private void show() {
75 | for (Map.Entry> project : tasks.entrySet()) {
76 | out.println(project.getKey());
77 | for (Task task : project.getValue()) {
78 | out.printf(" [%c] %d: %s%n", (task.isDone() ? 'x' : ' '), task.getId(), task.getDescription());
79 | }
80 | out.println();
81 | }
82 | }
83 |
84 | private void add(String commandLine) {
85 | String[] subcommandRest = commandLine.split(" ", 2);
86 | String subcommand = subcommandRest[0];
87 | if (subcommand.equals("project")) {
88 | addProject(subcommandRest[1]);
89 | } else if (subcommand.equals("task")) {
90 | String[] projectTask = subcommandRest[1].split(" ", 2);
91 | addTask(projectTask[0], projectTask[1]);
92 | }
93 | }
94 |
95 | private void addProject(String name) {
96 | tasks.put(name, new ArrayList());
97 | }
98 |
99 | private void addTask(String project, String description) {
100 | List projectTasks = tasks.get(project);
101 | if (projectTasks == null) {
102 | out.printf("Could not find a project with the name \"%s\".", project);
103 | out.println();
104 | return;
105 | }
106 | projectTasks.add(new Task(nextId(), description, false));
107 | }
108 |
109 | private void check(String idString) {
110 | setDone(idString, true);
111 | }
112 |
113 | private void uncheck(String idString) {
114 | setDone(idString, false);
115 | }
116 |
117 | private void setDone(String idString, boolean done) {
118 | int id = Integer.parseInt(idString);
119 | for (Map.Entry> project : tasks.entrySet()) {
120 | for (Task task : project.getValue()) {
121 | if (task.getId() == id) {
122 | task.setDone(done);
123 | return;
124 | }
125 | }
126 | }
127 | out.printf("Could not find a task with an ID of %d.", id);
128 | out.println();
129 | }
130 |
131 | private void help() {
132 | out.println("Commands:");
133 | out.println(" show");
134 | out.println(" add project ");
135 | out.println(" add task ");
136 | out.println(" check ");
137 | out.println(" uncheck ");
138 | out.println();
139 | }
140 |
141 | private void error(String command) {
142 | out.printf("I don't know what the command \"%s\" is.", command);
143 | out.println();
144 | }
145 |
146 | private long nextId() {
147 | return ++lastId;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/golang/src/task/list.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | "sort"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | /*
13 | * Features to add
14 | *
15 | * 1. Deadlines
16 | * (i) Give each task an optional deadline with the 'deadline ' command.
17 | * (ii) Show all tasks due today with the 'today' command.
18 | * 2. Customisable IDs
19 | * (i) Allow the user to specify an identifier that's not a number.
20 | * (ii) Disallow spaces and special characters from the ID.
21 | * 3. Deletion
22 | * (i) Allow users to delete tasks with the 'delete ' command.
23 | * 4. Views
24 | * (i) View tasks by date with the 'view by date' command.
25 | * (ii) View tasks by deadline with the 'view by deadline' command.
26 | * (iii) Don't remove the functionality that allows users to view tasks by project,
27 | * but change the command to 'view by project'
28 | */
29 |
30 | const (
31 | // Quit is the text command used to quit the task manager.
32 | Quit string = "quit"
33 | prompt string = "> "
34 | )
35 |
36 | // TaskList is a set of tasks, grouped by project.
37 | type TaskList struct {
38 | in io.Reader
39 | out io.Writer
40 |
41 | projectTasks map[string][]*Task
42 | lastID int64
43 | }
44 |
45 | // NewTaskList initializes a TaskList on the given I/O descriptors.
46 | func NewTaskList(in io.Reader, out io.Writer) *TaskList {
47 | return &TaskList{
48 | in: in,
49 | out: out,
50 | projectTasks: make(map[string][]*Task),
51 | lastID: 0,
52 | }
53 | }
54 |
55 | // Run runs the command loop of the task manager.
56 | // Sequentially executes any given command, until the user types the Quit message.
57 | func (l *TaskList) Run() {
58 | scanner := bufio.NewScanner(l.in)
59 |
60 | fmt.Fprint(l.out, prompt)
61 | for scanner.Scan() {
62 | cmdLine := scanner.Text()
63 | if cmdLine == Quit {
64 | return
65 | }
66 |
67 | l.execute(cmdLine)
68 | fmt.Fprint(l.out, prompt)
69 | }
70 | }
71 |
72 | func (l *TaskList) execute(cmdLine string) {
73 | args := strings.Split(cmdLine, " ")
74 | command := args[0]
75 | switch command {
76 | case "show":
77 | l.show()
78 | case "add":
79 | l.add(args[1:])
80 | case "check":
81 | l.check(args[1])
82 | case "uncheck":
83 | l.uncheck(args[1])
84 | case "help":
85 | l.help()
86 | default:
87 | l.error(command)
88 | }
89 | }
90 |
91 | func (l *TaskList) help() {
92 | fmt.Fprintln(l.out, `Commands:
93 | show
94 | add project
95 | add task
96 | check
97 | uncheck
98 | `)
99 | }
100 |
101 | func (l *TaskList) error(command string) {
102 | fmt.Fprintf(l.out, "Unknown command \"%s\".\n", command)
103 | }
104 |
105 | func (l *TaskList) show() {
106 | // sort projects (to make output deterministic)
107 | sortedProjects := make([]string, 0, len(l.projectTasks))
108 | for project := range l.projectTasks {
109 | sortedProjects = append(sortedProjects, project)
110 | }
111 | sort.Sort(sort.StringSlice(sortedProjects))
112 |
113 | // show projects sequentially
114 | for _, project := range sortedProjects {
115 | tasks := l.projectTasks[project]
116 | fmt.Fprintf(l.out, "%s\n", project)
117 | for _, task := range tasks {
118 | done := ' '
119 | if task.IsDone() {
120 | done = 'X'
121 | }
122 | fmt.Fprintf(l.out, " [%c] %d: %s\n", done, task.GetID(), task.GetDescription())
123 | }
124 | fmt.Fprintln(l.out)
125 | }
126 | }
127 |
128 | func (l *TaskList) add(args []string) {
129 | if len(args) < 2 {
130 | fmt.Fprintln(l.out, "Missing parameters for \"add\" command.")
131 | return
132 | }
133 | projectName := args[1]
134 | if args[0] == "project" {
135 | l.addProject(projectName)
136 | } else if args[0] == "task" {
137 | description := strings.Join(args[2:], " ")
138 | l.addTask(projectName, description)
139 | }
140 | }
141 |
142 | func (l *TaskList) addProject(name string) {
143 | l.projectTasks[name] = make([]*Task, 0)
144 | }
145 |
146 | func (l *TaskList) addTask(projectName, description string) {
147 | tasks, ok := l.projectTasks[projectName]
148 | if !ok {
149 | fmt.Fprintf(l.out, "Could not find a project with the name \"%s\".\n", projectName)
150 | return
151 | }
152 | l.projectTasks[projectName] = append(tasks, NewTask(l.nextID(), description, false))
153 | }
154 |
155 | func (l *TaskList) check(idString string) {
156 | l.setDone(idString, true)
157 | }
158 |
159 | func (l *TaskList) uncheck(idString string) {
160 | l.setDone(idString, false)
161 | }
162 |
163 | func (l *TaskList) setDone(idString string, done bool) {
164 | id, err := strconv.ParseInt(idString, 10, 64)
165 | if err != nil {
166 | fmt.Fprintf(l.out, "Invalid ID \"%s\".\n", idString)
167 | return
168 | }
169 |
170 | for _, tasks := range l.projectTasks {
171 | for _, task := range tasks {
172 | if task.GetID() == id {
173 | task.SetDone(done)
174 | return
175 | }
176 | }
177 | }
178 |
179 | fmt.Fprintf(l.out, "Task with ID \"%d\" not found.\n", id)
180 | }
181 |
182 | func (l *TaskList) nextID() int64 {
183 | l.lastID++
184 | return l.lastID
185 | }
186 |
--------------------------------------------------------------------------------
/typescript/src/task_list.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import readline = require('readline');
4 | import util = require('util');
5 |
6 | import task = require('./task');
7 |
8 | function splitFirstSpace(s: string) {
9 | var pos = s.indexOf(' ');
10 | if(pos === -1) {
11 | return [s];
12 | }
13 | return [s.substr(0, pos), s.substr(pos+1)]
14 | }
15 |
16 | export class TaskList
17 | {
18 | static QUIT = 'quit';
19 | private readline;
20 | private tasks: {[index: string]: task.Task[]} = {};
21 | private lastId = 0;
22 |
23 | constructor(reader: NodeJS.ReadableStream, writer: NodeJS.WritableStream) {
24 |
25 | this.readline = readline.createInterface({
26 | terminal: false,
27 | input: reader,
28 | output: writer
29 | });
30 |
31 | this.readline.setPrompt("> ");
32 | this.readline.on('line', (cmd) => {
33 | if(cmd == TaskList.QUIT) {
34 | this.readline.close();
35 | return;
36 | }
37 | this.execute(cmd);
38 | this.readline.prompt();
39 | });
40 | this.readline.on('close', () => {
41 | writer.end();
42 | });
43 | }
44 |
45 | println(ln: string) {
46 | this.readline.output.write(ln);
47 | this.readline.output.write('\n');
48 | }
49 |
50 | run() {
51 | this.readline.prompt();
52 | }
53 |
54 | forEachProject(func: (key: string, value: task.Task[]) => any) {
55 | for(var key in this.tasks) {
56 | if(this.tasks.hasOwnProperty(key))
57 | func(key, this.tasks[key])
58 | }
59 | }
60 |
61 | execute(commandLine: string) {
62 | var commandRest = splitFirstSpace(commandLine);
63 | var command = commandRest[0];
64 | switch (command) {
65 | case "show":
66 | this.show();
67 | break;
68 | case "add":
69 | this.add(commandRest[1]);
70 | break;
71 | case "check":
72 | this.check(commandRest[1]);
73 | break;
74 | case "uncheck":
75 | this.uncheck(commandRest[1]);
76 | break;
77 | case "help":
78 | this.help();
79 | break;
80 | default:
81 | this.error(command);
82 | break;
83 | }
84 | }
85 |
86 | private show() {
87 | this.forEachProject((project, taskList) => {
88 | this.println(project);
89 | taskList.forEach((task) => {
90 | this.println(util.format(" [%s] %d: %s", (task.done ? 'x' : ' '), task.id, task.description));
91 | });
92 | this.println('');
93 | });
94 | }
95 |
96 | private add(commandLine: string) {
97 | var subcommandRest = splitFirstSpace(commandLine);
98 | var subcommand = subcommandRest[0];
99 | if (subcommand === "project") {
100 | this.addProject(subcommandRest[1]);
101 | } else if (subcommand === "task") {
102 | var projectTask = splitFirstSpace(subcommandRest[1]);
103 | this.addTask(projectTask[0], projectTask[1]);
104 | }
105 | }
106 |
107 | private addProject(name: string) {
108 | this.tasks[name] = [];
109 | }
110 |
111 | private addTask(project: string, description: string) {
112 | var projectTasks = this.tasks[project];
113 | if (projectTasks == null) {
114 | this.println(util.format("Could not find a project with the name \"%s\".", project));
115 | return;
116 | }
117 | projectTasks.push(new task.Task(this.nextId(), description, false));
118 | }
119 |
120 | private check(idString: string) {
121 | this.setDone(idString, true);
122 | }
123 |
124 | private uncheck(idString: string) {
125 | this.setDone(idString, false);
126 | }
127 |
128 | private setDone(idString: string, done: boolean) {
129 | var id = parseInt(idString, 10);
130 | var found = false;
131 | this.forEachProject((project, taskList) => {
132 | taskList.forEach((task) => {
133 | if (task.id == id) {
134 | task.done = done;
135 | found = true;
136 | }
137 | });
138 | });
139 | if(!found)
140 | this.println(util.format("Could not find a task with an ID of %d.", id));
141 | }
142 |
143 | private help() {
144 | this.println("Commands:");
145 | this.println(" show");
146 | this.println(" add project ");
147 | this.println(" add task ");
148 | this.println(" check ");
149 | this.println(" uncheck ");
150 | this.println("");
151 | }
152 |
153 | private error(command: string) {
154 | this.println('I don\'t know what the command "' + command + '" is.');
155 | }
156 |
157 | private nextId(): number {
158 | return ++this.lastId;
159 | }
160 | }
161 |
162 | if(require.main == module) {
163 | new TaskList(process.stdin, process.stdout).run()
164 | }
165 |
--------------------------------------------------------------------------------