taskmeister 0.0.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a586411f621cb4f407507210138370a695cc47c1
4
- data.tar.gz: 0b0ad5564af309017e0960f48ca5b744bdb65421
3
+ metadata.gz: 9765c5b79d2cce230bd4cdc78b11aba686f91adc
4
+ data.tar.gz: 7a1f828b681135fbedb8fc8209c6cd799fdf804d
5
5
  SHA512:
6
- metadata.gz: 4cea1c245ba6fe4d21ef1f24fd3f6cbcb4fe11c132dd615a18104ebc997c6acf0cea80a237649d5f407a1fc11c99fdb8f626bcaf18844e9668958213faf0ca34
7
- data.tar.gz: 39006a1aef684454313d598b7bbf4f3382ced4939209ed3e6932c61272e02fe04957a7057fea8f4164d01235644d63ba09db8014d722aca6eb42e618812f8acb
6
+ metadata.gz: 3e92cac2efc981db979f8723c8a0a4590f5e42674779e1393d234523846c1b23574d6f91ac1042821c58a08e94df5f15c49c7d147f47e4722571cb913a22507a
7
+ data.tar.gz: 193f0e2d6df20a5961622d1d4c0be8e5183773b4ba9a4b8ed149fa5fffd37cd85d0cc76567c46213731f9712e2066a56d72af97f8dfcb4f43fc22cf56680e7f7
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/README.md CHANGED
@@ -1,38 +1,79 @@
1
1
  # Taskmeister
2
2
 
3
- Simple task management modelled after [t](http://stevelosh.com/projects/t/).
3
+ Simple command line task management modelled after [t](http://stevelosh.com/projects/t/).
4
4
 
5
- ## Installation
5
+ I like the simplicity of t but there are a couple of changes I wanted.
6
+
7
+ First: I like to have separate task lists per project. So I infer the task list
8
+ name from the current project directory. It finds a project directory by walking
9
+ up from the current directory until it finds a `.git` or `.hg` directory in it.
10
+
11
+ Second: I like to have a short list of notes accompanying my tasks. You can add
12
+ notes beneath your task. They can be edited via Vim with `taskmeister -e` or
13
+ `taskmeister -e id` to seek to a specific task.
6
14
 
7
- Add this line to your application's Gemfile:
15
+ Third: I format my task files in Markdown so that when I open them on my phone
16
+ via a Markdown-enabled editor they look nice. The format is still simple:
8
17
 
9
- gem 'taskmeister'
18
+ ```markdown
19
+ Add authorisation - [id](aaf83a9b-02f7-4cc0-8ee1-4d98b98903b8)
10
20
 
11
- And then execute:
21
+ > Some notes to go with the task above.
22
+ >
23
+ > Maybe paste in some links or other interesting information for later.
12
24
 
13
- $ bundle
25
+ Refactor the task model - [id](ae0cce15-456d-48c0-a2e2-69d5f567e092)
26
+ Update the README - [id](a5d4d3a9-2b9a-427a-9047-b47c6aec8f93)
27
+ ```
14
28
 
15
- Or install it yourself as:
29
+ ## Installation
30
+
31
+ Install via Rubygems:
16
32
 
17
- $ gem install taskmeister
33
+ ```sh
34
+ $ gem install taskmeister
35
+ ```
18
36
 
19
37
  ## Usage
20
38
 
21
- TODO: Write usage instructions here
39
+ ```sh
40
+ $ taskmeister --help
41
+ Usage: taskmeister [options] TASK TEXT
42
+
43
+ Specific options:
44
+ If no options are specified TASK TEXT is added as a new task.
22
45
 
23
- ## Notes
46
+ -t, --task-dir DIRECTORY The DIRECTORY where your task lists are stored. (Defaults to pwd)
47
+ -l, --list NAME The task list to use.
48
+ Will use a list named after your current project directory if not supplied.
49
+ A project directory is found by walking up from the current directory and stopping if a .git or .hg directory is found.
50
+ -d, --done TASK_ID Finish a task
51
+ -s, --show TASK_ID Show a task list item and its notes
52
+ -e, --edit TASK_ID Edit a task in Vim
53
+ -r, --replace TASK_ID Replace a task description
24
54
 
25
- Just trigger vim to edit the file in place with the following commands
55
+ Common options:
56
+ -h, --help Show this message
57
+ --version Show version
58
+ ```
26
59
 
27
- "${EDITOR:-vi}" file.txt
28
- vim +4 README.md
29
- vim +/killproc file.md
30
- vim +/guid file.md
60
+ I store my task files in Dropbox so I have the following shell alias set:
61
+
62
+ ```sh
63
+ alias t='taskmeister -t ~/Dropbox/Tasks'
64
+ ```
65
+
66
+ I let Taskmeister determine the task list name from my current directory. If
67
+ you want a set a specific task list you could add that to your alias.
68
+
69
+ ```sh
70
+ alias t='taskmeister -t ~/Dropbox/Tasks -l mytasks.md'
71
+ ```
31
72
 
32
73
  ## Contributing
33
74
 
34
- 1. Fork it ( https://github.com/[my-github-username]/taskmeister/fork )
35
- 2. Create your feature branch (`git checkout -b my-new-feature`)
36
- 3. Commit your changes (`git commit -am 'Add some feature'`)
37
- 4. Push to the branch (`git push origin my-new-feature`)
38
- 5. Create a new Pull Request
75
+ Fork the project on Github, add tests for your changes, and submit a well described pull request.
76
+
77
+ ## Copyright
78
+
79
+ Copyright (c) 2014 Ray Grasso. See LICENSE.txt for further details.
data/Rakefile CHANGED
@@ -1,2 +1,12 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
4
+ require 'cucumber/rake/task'
5
+
6
+ Cucumber::Rake::Task.new
7
+
8
+ require 'rspec/core/rake_task'
9
+
10
+ RSpec::Core::RakeTask.new(:spec)
11
+
12
+ task :default => :spec
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH << File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
4
4
 
5
- require 'taskmeister'
5
+ # Exit cleanly from an early interrupt
6
+ Signal.trap("INT") { exit 1 }
6
7
 
7
- puts "Coming soon..."
8
+ require "taskmeister"
9
+
10
+ Taskmeister::Cli::Main.new(ARGV.dup).execute!
@@ -0,0 +1,17 @@
1
+ Feature: taskmeister adds a task to the list
2
+
3
+ Scenario: Project directory with task list
4
+ Given a file named "mylist.md" with:
5
+ """
6
+ Task one - [id](aaf83a9b-02f7-4cc0-8ee1-4d98b98903b8)
7
+
8
+ > Notes line one
9
+ > Notes line two
10
+
11
+ Task two - [id](ae0cce15-456d-48c0-a2e2-69d5f567e092)
12
+ Task three - [id](a5d4d3a9-2b9a-427a-9047-b47c6aec8f93)
13
+ """
14
+ When I successfully run `taskmeister --list mylist.md A new task`
15
+ And I successfully run `taskmeister --list mylist.md`
16
+ Then the output should contain "A new task"
17
+
@@ -0,0 +1,17 @@
1
+ Feature: taskmeister removes finished tasks from the list
2
+
3
+ Scenario: Project directory with task list
4
+ Given a file named "mylist.md" with:
5
+ """
6
+ Task one - [id](aaf83a9b-02f7-4cc0-8ee1-4d98b98903b8)
7
+
8
+ > Notes line one
9
+ > Notes line two
10
+
11
+ Task two - [id](ae0cce15-456d-48c0-a2e2-69d5f567e092)
12
+ Task three - [id](a5d4d3a9-2b9a-427a-9047-b47c6aec8f93)
13
+ """
14
+ When I successfully run `taskmeister --list mylist.md --done a`
15
+ And I successfully run `taskmeister --list mylist.md`
16
+ Then the output should not contain "Task one"
17
+
@@ -0,0 +1,18 @@
1
+ Feature: taskmeister lists the contents of a task list
2
+
3
+ Scenario: Normal directory with explicit task list
4
+ Given a file named "mylist.md" with:
5
+ """
6
+ Task one - [id](aaf83a9b-02f7-4cc0-8ee1-4d98b98903b8)
7
+
8
+ > Notes line one
9
+ > Notes line two
10
+
11
+ Task two - [id](ae0cce15-456d-48c0-a2e2-69d5f567e092)
12
+ Task three - [id](a5d4d3a9-2b9a-427a-9047-b47c6aec8f93)
13
+ """
14
+ When I successfully run `taskmeister --list mylist.md`
15
+ Then the output should contain "a - Task one »"
16
+ And the output should contain "ae - Task two"
17
+ And the output should contain "a5 - Task three"
18
+
@@ -0,0 +1,22 @@
1
+ Feature: taskmeister infers the name of your task list from your current project directory
2
+
3
+ Scenario: Project directory with task list
4
+ Given a directory named "project"
5
+ And a directory named "project/.git"
6
+ And a directory named "project/child"
7
+ And a file named "project.md" with:
8
+ """
9
+ Task one - [id](aaf83a9b-02f7-4cc0-8ee1-4d98b98903b8)
10
+
11
+ > Notes line one
12
+ > Notes line two
13
+
14
+ Task two - [id](ae0cce15-456d-48c0-a2e2-69d5f567e092)
15
+ Task three - [id](a5d4d3a9-2b9a-427a-9047-b47c6aec8f93)
16
+ """
17
+ And I cd to "project/child"
18
+ When I successfully run `taskmeister --task-dir ../../`
19
+ Then the output should contain "a - Task one »"
20
+ And the output should contain "ae - Task two"
21
+ And the output should contain "a5 - Task three"
22
+
@@ -0,0 +1,17 @@
1
+ Feature: taskmeister replaces a task in the list
2
+
3
+ Scenario: Project directory with task list
4
+ Given a file named "mylist.md" with:
5
+ """
6
+ Task one - [id](aaf83a9b-02f7-4cc0-8ee1-4d98b98903b8)
7
+
8
+ > Notes line one
9
+ > Notes line two
10
+
11
+ Task two - [id](ae0cce15-456d-48c0-a2e2-69d5f567e092)
12
+ Task three - [id](a5d4d3a9-2b9a-427a-9047-b47c6aec8f93)
13
+ """
14
+ When I successfully run `taskmeister --list mylist.md -r a A new task`
15
+ And I successfully run `taskmeister --list mylist.md`
16
+ Then the output should contain "a - A new task"
17
+
@@ -0,0 +1,18 @@
1
+ Feature: taskmeister shows the details of a task in the list
2
+
3
+ Scenario: Project directory with task list
4
+ Given a file named "mylist.md" with:
5
+ """
6
+ Task one - [id](aaf83a9b-02f7-4cc0-8ee1-4d98b98903b8)
7
+
8
+ > Notes line one
9
+ > Notes line two
10
+
11
+ Task two - [id](ae0cce15-456d-48c0-a2e2-69d5f567e092)
12
+ Task three - [id](a5d4d3a9-2b9a-427a-9047-b47c6aec8f93)
13
+ """
14
+ When I successfully run `taskmeister --list mylist.md --show a`
15
+ Then the output should contain "Task one - [id](aaf83a9b-02f7-4cc0-8ee1-4d98b98903b8)"
16
+ And the output should contain "> Notes line one"
17
+ And the output should contain "> Notes line two"
18
+
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'aruba/cucumber'
5
+
6
+ $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
7
+ require 'taskmeister'
8
+ require 'fileutils'
9
+
10
+ require 'aruba'
11
+ require 'aruba/in_process'
12
+
13
+ Aruba::InProcess.main_class = Taskmeister::Cli::Main
14
+ Aruba.process = Aruba::InProcess
@@ -1,5 +1,8 @@
1
1
  require "taskmeister/version"
2
-
3
- module Taskmeister
4
- # Your code goes here...
5
- end
2
+ require "taskmeister/task"
3
+ require "taskmeister/task_list"
4
+ require "taskmeister/task_list_reader"
5
+ require "taskmeister/task_list_writer"
6
+ require "taskmeister/cli/task_list_name"
7
+ require "taskmeister/cli/main"
8
+ require "taskmeister/cli/options"
@@ -0,0 +1,77 @@
1
+ require "pathname"
2
+
3
+ module Taskmeister
4
+ module Cli
5
+ class Main
6
+ def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
7
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
8
+ end
9
+
10
+ def execute!
11
+ options = Options.new(@stdout, @kernel).parse(@argv)
12
+
13
+ task_list_path = Pathname.new(options.task_dir) + task_list_name(options)
14
+
15
+ task_list = Taskmeister::TaskListReader.from_markdown_file(task_list_path)
16
+
17
+ run_command(options, task_list_path, task_list)
18
+ end
19
+
20
+ private
21
+
22
+ def task_list_name(options)
23
+ task_list_name = options.list || TaskListName.from_project_dir(Pathname.getwd)
24
+
25
+ unless task_list_name
26
+ @stdout.puts "Could not find a project directory. Please specify a task list."
27
+ @kernel.exit 1
28
+ end
29
+
30
+ task_list_name
31
+ end
32
+
33
+ def run_command(options, task_list_path, task_list)
34
+ case options.command
35
+ when Commands::LIST
36
+ @stdout.puts task_list.to_short_list
37
+
38
+ when Commands::SHOW
39
+
40
+ @stdout.puts task_list.details(options.task_id)
41
+
42
+ when Commands::EDIT
43
+
44
+ task = task_list[options.task_id]
45
+
46
+ search_path = task ? "+/#{task.id} " : ""
47
+ system "vim #{search_path}#{task_list_path}"
48
+
49
+ when Commands::ADD
50
+
51
+ update_task_list(task_list, task_list_path) {
52
+ task_list.add(Taskmeister::Task.create(options.task_text))
53
+ }
54
+
55
+ when Commands::DONE
56
+
57
+ update_task_list(task_list, task_list_path) {
58
+ task_list.complete options.task_id
59
+ }
60
+
61
+ when Commands::REPLACE
62
+
63
+ update_task_list(task_list, task_list_path) {
64
+ task_list.replace options.task_id, options.task_text
65
+ }
66
+
67
+ end
68
+
69
+ @kernel.exit 0
70
+ end
71
+
72
+ def update_task_list(task_list, file_path)
73
+ Taskmeister::TaskListWriter.to_markdown_file(task_list, file_path) if yield
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,108 @@
1
+ require "optparse"
2
+ require "ostruct"
3
+ require "pathname"
4
+
5
+ module Taskmeister
6
+ module Cli
7
+ module Commands
8
+ ADD = "add"
9
+ LIST = "list"
10
+ REPLACE = "replace"
11
+ EDIT = "edit"
12
+ SHOW = "show"
13
+ DONE = "done"
14
+ end
15
+
16
+ class Options
17
+ def initialize(stdout = STDOUT, kernel = Kernel)
18
+ @stdout = stdout
19
+ @kernel = kernel
20
+ end
21
+
22
+ def parse(args)
23
+ options = default_options
24
+
25
+ opt_parser = OptionParser.new do |opts|
26
+ opts.program_name = "taskmeister"
27
+ opts.banner = "Usage: taskmeister [options] TASK TEXT"
28
+
29
+ opts.separator ""
30
+ opts.separator "Specific options:"
31
+ opts.separator " If no options are specified TASK TEXT is added as a new task."
32
+ opts.separator ""
33
+
34
+ opts.on("-t", "--task-dir DIRECTORY",
35
+ "The DIRECTORY where your task lists are stored. (Defaults to pwd)") do |dir|
36
+ options.task_dir = Pathname.new(dir)
37
+ end
38
+
39
+ opts.on("-l", "--list NAME",
40
+ "The task list to use.",
41
+ " Will use a list named after your current project directory if not supplied.",
42
+ " A project directory is found by walking up from the current directory and stopping if a .git or .hg directory is found.") do |list|
43
+ options.list = Pathname.new(list)
44
+ end
45
+
46
+ opts.on("-d", "--done TASK_ID",
47
+ "Finish a task") do |task_id|
48
+ options.command = Commands::DONE
49
+ options.task_id = task_id
50
+ end
51
+
52
+ opts.on("-s", "--show TASK_ID",
53
+ "Show a task list item and its notes") do |task_id|
54
+ options.command = Commands::SHOW
55
+ options.task_id = task_id
56
+ end
57
+
58
+ opts.on("-e", "--edit [TASK_ID]",
59
+ "Edit task list in Vim",
60
+ " Will search for a specific task if TASK_ID is provided") do |task_id|
61
+ options.command = Commands::EDIT
62
+ options.task_id = task_id
63
+ end
64
+
65
+ opts.on("-r", "--replace TASK_ID",
66
+ "Replace a task description") do |task_id|
67
+ options.command = Commands::REPLACE
68
+ options.task_id = task_id
69
+ end
70
+
71
+ opts.separator ""
72
+ opts.separator "Common options:"
73
+
74
+ opts.on_tail("-h", "--help", "Show this message") do
75
+ @stdout.puts opts
76
+ @kernel.exit
77
+ end
78
+
79
+ opts.on_tail("--version", "Show version") do
80
+ @stdout.puts Taskmeister::VERSION
81
+ @kernel.exit
82
+ end
83
+ end
84
+
85
+ task_text = opt_parser.parse!(args)
86
+
87
+ options.task_text = task_text.join(" ") unless task_text.empty?
88
+
89
+ # If there is task text and the default command hasn't been overwritten
90
+ # by the user, make the command add
91
+ if !task_text.empty? and options.command == Commands::LIST
92
+ options.command = Commands::ADD
93
+ end
94
+
95
+ options
96
+ end
97
+
98
+ private
99
+
100
+ def default_options
101
+ OpenStruct.new.tap do |o|
102
+ o.command = Commands::LIST
103
+ o.task_dir = Pathname.getwd
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,28 @@
1
+ require "pathname"
2
+
3
+ module Taskmeister
4
+ module Cli
5
+ class TaskListName
6
+ def self.from_project_dir(dir)
7
+ project_dir = find_project_dir(dir)
8
+
9
+ return project_dir.basename.to_s + ".md" if project_dir
10
+ end
11
+
12
+ def self.find_project_dir(dir)
13
+ return dir if dir.children.any? { |child| is_project_dir?(child) }
14
+
15
+ return nil if dir == Pathname.new("/") || dir.parent.nil?
16
+
17
+ return self.find_project_dir(dir.parent)
18
+ end
19
+
20
+ def self.is_project_dir?(dir)
21
+ dir.directory? && (
22
+ dir.basename == Pathname.new(".git") ||
23
+ dir.basename == Pathname.new(".hg")
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ require 'securerandom'
2
+
3
+ module Taskmeister
4
+ class Task
5
+ attr_reader :text, :notes, :id
6
+
7
+ def initialize(text, id, notes)
8
+ @text, @id, @notes = text, id, notes
9
+ end
10
+
11
+ def notes?
12
+ notes && !notes.empty?
13
+ end
14
+
15
+ def to_markdown
16
+ [ "#{text} - [id](#{id})" ].tap do |a|
17
+ return a unless notes.match(/\S/)
18
+ a << ""
19
+ a.concat notes.split("\n").map { |n|
20
+ n.size > 0 ? "> #{n}" : ">"
21
+ }
22
+ a << ""
23
+ end
24
+ end
25
+
26
+ def self.create(text)
27
+ self.new(text, SecureRandom.uuid, "")
28
+ end
29
+
30
+ def self.from_markdown(lines)
31
+ task, *notes = *lines
32
+
33
+ text, id = task_attributes(task)
34
+
35
+ notes = notes.map { |l| l.gsub(/\A> ?/, "") }.join("\n")
36
+
37
+ self.new(text, id, notes)
38
+ end
39
+
40
+ def self.task_attributes(line)
41
+ matches = line.match(/\A(.+) - \[id\]\(([\w-]+)\)\z/)
42
+
43
+ fail "Invalid task: #{line}" unless matches
44
+
45
+ [matches[1], matches[2]]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ module Taskmeister
2
+ class TaskList
3
+ def initialize(tasks)
4
+ @hash = {}
5
+
6
+ tasks.each do |t|
7
+ add(t)
8
+ end
9
+ end
10
+
11
+ def to_short_list
12
+ longest_id = @hash.keys.max_by(&:length)
13
+ @hash.map { |id, task|
14
+ marker = task.notes? ? " »" : ""
15
+ "%-#{longest_id.length}s - %s%s" % [id, task.text, marker]
16
+ }
17
+ end
18
+
19
+ def tasks
20
+ @hash.values
21
+ end
22
+
23
+ def [](key)
24
+ @hash[key]
25
+ end
26
+
27
+ def add(task)
28
+ prefix = assign_short_code_to_task(task)
29
+ @hash[prefix] = task
30
+ end
31
+
32
+ def complete(short_id)
33
+ @hash.delete(short_id)
34
+ end
35
+
36
+ def replace(short_id, new_text)
37
+ task = self[short_id]
38
+ return unless task
39
+
40
+ @hash[short_id] = Task.new(new_text, task.id, task.notes)
41
+ end
42
+
43
+ def details(short_id)
44
+ return [] unless @hash.has_key?(short_id)
45
+
46
+ self[short_id].to_markdown
47
+ end
48
+
49
+ private
50
+
51
+ def assign_short_code_to_task(task)
52
+ task.id.length.times do |i|
53
+ prefix = task.id.slice(0, i + 1)
54
+ return prefix unless @hash.has_key?(prefix)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ module Taskmeister
2
+ class TaskListReader
3
+ def self.from_markdown(file_lines)
4
+ grouped_lines = \
5
+ file_lines.map(&:chomp)
6
+ .reject(&:empty?)
7
+ .reduce([]) do |acc, l|
8
+ acc << [l] if l.match(/\A[^\s>]/) # A new task
9
+ acc.last << l if l.match(/\A>/) # A line of note for the latest task
10
+ acc
11
+ end
12
+
13
+ tasks = grouped_lines.map do |l|
14
+ Task.from_markdown(l)
15
+ end
16
+
17
+ TaskList.new tasks
18
+ end
19
+
20
+ def self.from_markdown_file(path)
21
+ lines = File.exist?(path) ? File.readlines(path) : []
22
+ from_markdown lines
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ module Taskmeister
2
+ class TaskListWriter
3
+ def self.to_markdown(task_list)
4
+ task_list.tasks.map(&:to_markdown).flatten
5
+ end
6
+
7
+ def self.to_markdown_file(task_list, file_path)
8
+ lines = self.to_markdown(task_list)
9
+ File.open(file_path, "w") do |f|
10
+ f.puts lines
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Taskmeister
2
- VERSION = "0.0.1"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -0,0 +1,77 @@
1
+ require "taskmeister/task_list_reader"
2
+ require "taskmeister/task_list"
3
+ require "taskmeister/task"
4
+
5
+ module Taskmeister
6
+ RSpec.describe TaskListReader do
7
+ describe ".from_markdown" do
8
+
9
+ before do
10
+ allow(Task).to receive(:from_markdown).and_return(double(Task))
11
+ allow(TaskList).to receive(:new).and_return(double(TaskList))
12
+ end
13
+
14
+ let(:it) { described_class.from_markdown(lines) }
15
+
16
+ describe "passed an empty list of lines" do
17
+ let(:lines) { [] }
18
+
19
+ it "creates an empty task list" do
20
+ expect(Task).not_to receive(:from_markdown)
21
+ expect(TaskList).to receive(:new).with([])
22
+ it
23
+ end
24
+ end
25
+
26
+ describe "passed a list of simple tasks" do
27
+ let(:lines) { [
28
+ "Task number 1\n",
29
+ "Task number 2\n"
30
+ ]
31
+ }
32
+
33
+ it "creates a task for each line, stripping new lines" do
34
+ expect(Task).to receive(:from_markdown).with([ "Task number 1" ])
35
+ expect(Task).to receive(:from_markdown).with([ "Task number 2" ])
36
+ it
37
+ end
38
+ end
39
+
40
+ describe "passed a list of tasks with notes" do
41
+ let(:lines) { [
42
+ "Task number 1\n",
43
+ "\n",
44
+ "> note line 1\n",
45
+ "> note line 2\n",
46
+ "\n",
47
+ "Task number 2\n",
48
+ "Task number 3\n",
49
+ "\n",
50
+ "> note line 3\n",
51
+ "> note line 4\n",
52
+ "",
53
+ "",
54
+ ]
55
+ }
56
+
57
+ it "creates a task for each line with their associated notes, stripping newlines" do
58
+ expect(Task).to receive(:from_markdown).with([
59
+ "Task number 1",
60
+ "> note line 1",
61
+ "> note line 2",
62
+ ])
63
+ expect(Task).to receive(:from_markdown).with([
64
+ "Task number 2"
65
+ ])
66
+ expect(Task).to receive(:from_markdown).with([
67
+ "Task number 3",
68
+ "> note line 3",
69
+ "> note line 4",
70
+ ])
71
+
72
+ it
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,106 @@
1
+ require "taskmeister/task_list"
2
+ require "taskmeister/task"
3
+
4
+ module Taskmeister
5
+ RSpec.describe TaskList do
6
+
7
+ let(:task1) { Task.new("task 1", "78dc2561-8c9a-4780-a560-56e1e14f2ed3", nil) }
8
+ let(:task2) { Task.new("task 2", "b2110029-ffa0-4e54-985a-2aa6d1bed53b", nil) }
9
+ let(:task3) { Task.new("task 3", "1c890df0-97a5-4310-9b01-374983416af7", "a note") }
10
+ let(:task4) { Task.new("task 4", "1c891df0-97a5-4310-9b01-374983416af7", nil) }
11
+ let(:task5) { Task.new("task 5", "ef0915ee-3d11-4358-b12c-15a4ab0d1d26", nil) }
12
+ let(:task6) { Task.new("task 6", "f2a0d281-f323-4909-8547-d4af5315a295", nil) }
13
+ let(:task7) { Task.new("task 7", "f2a0d281-4323-4909-8547-d4af5315a295", nil) }
14
+
15
+ let(:tasks) { [
16
+ task1, task2, task3, task4, task5, task6, task7
17
+ ]}
18
+
19
+ let(:list) { described_class.new(tasks) }
20
+
21
+ describe "#[]" do
22
+ it "uses the shortest common prefix to index a task" do
23
+ expect(list["7"]).to eq task1
24
+ expect(list["b"]).to eq task2
25
+ expect(list["1"]).to eq task3
26
+ expect(list["1c"]).to eq task4
27
+ expect(list["e"]).to eq task5
28
+ expect(list["f"]).to eq task6
29
+ expect(list["f2"]).to eq task7
30
+ end
31
+ end
32
+
33
+ describe "#to_short_list" do
34
+ subject { list.to_short_list }
35
+
36
+ it "returns an array of short ids and task texts with markers for those that have notes" do
37
+ expect(subject).to eq([
38
+ "7 - task 1",
39
+ "b - task 2",
40
+ "1 - task 3 »",
41
+ "1c - task 4",
42
+ "e - task 5",
43
+ "f - task 6",
44
+ "f2 - task 7"
45
+ ])
46
+ end
47
+ end
48
+
49
+ describe "#complete" do
50
+ it "removes the identified task" do
51
+ expect(list["7"]).to eq task1
52
+ result = list.complete("7")
53
+ expect(result).to be_truthy
54
+ expect(list["7"]).to be_nil
55
+ end
56
+
57
+ it "ignores non-existent tasks" do
58
+ result = list.complete("345")
59
+ expect(result).to be_falsy
60
+ expect(list.tasks.size).to eq tasks.size
61
+ end
62
+ end
63
+
64
+ describe "#replace" do
65
+ it "replaces the text of the specified task" do
66
+ result = list.replace("7", "A new task text")
67
+ expect(result).to be_truthy
68
+ task = list["7"]
69
+ expect(task.text).to eq "A new task text"
70
+ end
71
+
72
+ it "ignores a non-existent task" do
73
+ result = list.replace("345", "A new task text")
74
+ expect(result).to be_falsy
75
+ expect(list.tasks.size).to eq tasks.size
76
+ end
77
+ end
78
+
79
+ describe "#details" do
80
+ before do
81
+ allow_any_instance_of(Task).to receive(:to_markdown).and_return([
82
+ "line 1",
83
+ "line 2"
84
+ ])
85
+ end
86
+
87
+ it "returns an empty list if the id doesn't exist" do
88
+ expect(list.details("34")).to eq []
89
+ end
90
+
91
+ it "returns the lines of a task" do
92
+ expect(list.details("7")).to eq ["line 1", "line 2"]
93
+ end
94
+ end
95
+
96
+ describe "#add" do
97
+ it "adds the task and assigns it a short id" do
98
+ task8 = Task.new("task 8", "f23891df0-97a5-4310-9b01-374983416af7", nil)
99
+ result = list.add(task8)
100
+ expect(result).to be_truthy
101
+ expect(list.tasks.size).to eq 8
102
+ expect(list["f23"]).to eq task8
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,25 @@
1
+ require "taskmeister/task_list_writer"
2
+ require "taskmeister/task_list"
3
+ require "taskmeister/task"
4
+
5
+ module Taskmeister
6
+ RSpec.describe TaskListWriter do
7
+ describe ".to_markdown" do
8
+ let(:task1) { double(Task, to_markdown: ["line1", "line2"]) }
9
+ let(:task2) { double(Task, to_markdown: ["line3", "line4"]) }
10
+ let(:task_list) { double(TaskList, tasks: [ task1, task2 ]) }
11
+
12
+ subject { described_class.to_markdown(task_list) }
13
+
14
+ it "returns a concatenated list of all the underlying task markdown" do
15
+ expect(subject).to eq [
16
+ "line1",
17
+ "line2",
18
+ "line3",
19
+ "line4"
20
+ ]
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,81 @@
1
+ require "taskmeister/task"
2
+
3
+ module Taskmeister
4
+ RSpec.describe Task do
5
+ describe ".from_markdown" do
6
+ subject { described_class.from_markdown(lines) }
7
+
8
+ describe "passed a single valid line" do
9
+ let(:lines) { [
10
+ "A task name - [id](78dc2561-8c9a-4780-a560-56e1e14f2ed3)"
11
+ ]}
12
+
13
+ it "sets the name and id of the task" do
14
+ expect(subject.id).to eq "78dc2561-8c9a-4780-a560-56e1e14f2ed3"
15
+ expect(subject.text).to eq "A task name"
16
+ expect(subject.notes).to eq ""
17
+ end
18
+ end
19
+
20
+ describe "passed a single valid line and notes" do
21
+ let(:lines) { [
22
+ "A task name - [id](78dc2561-8c9a-4780-a560-56e1e14f2ed3)",
23
+ "> line one of a note",
24
+ ">",
25
+ "> line two of a note"
26
+ ]}
27
+
28
+ it "sets the name and id of the task" do
29
+ expect(subject.id).to eq "78dc2561-8c9a-4780-a560-56e1e14f2ed3"
30
+ expect(subject.text).to eq "A task name"
31
+ end
32
+
33
+ it "concatenates together the notes of the task" do
34
+ expect(subject.notes).to eq "line one of a note\n\nline two of a note"
35
+ end
36
+ end
37
+
38
+ describe "passed a single invalid line" do
39
+ let(:lines) { [
40
+ "A task name"
41
+ ]}
42
+
43
+ it "raises an error" do
44
+ expect{ subject }.to raise_error
45
+ end
46
+ end
47
+ end
48
+
49
+ describe ".create" do
50
+ it "returns a task with the specified text and a new id" do
51
+ task = described_class.create("My task text")
52
+ expect(task.text).to eq "My task text"
53
+ expect(task.id).to match(/[\w-]+/)
54
+ expect(task.notes).to eq ""
55
+ end
56
+ end
57
+
58
+ describe "#to_markdown" do
59
+ it "returns a list of markdown-formatted lines" do
60
+ task = described_class.new("task 8", "f23891df0-97a5-4310-9b01-374983416af7", " ")
61
+ expect(task.to_markdown).to eq [
62
+ "task 8 - [id](f23891df0-97a5-4310-9b01-374983416af7)"
63
+ ]
64
+ end
65
+
66
+ it "returns a list of markdown-formatted lines including notes" do
67
+ task = described_class.new("task 8", "f23891df0-97a5-4310-9b01-374983416af7",
68
+ "First line of a note\nsecond line\n\nthird line")
69
+ expect(task.to_markdown).to eq [
70
+ "task 8 - [id](f23891df0-97a5-4310-9b01-374983416af7)",
71
+ "",
72
+ "> First line of a note",
73
+ "> second line",
74
+ ">",
75
+ "> third line",
76
+ ""
77
+ ]
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,46 @@
1
+ RSpec.configure do |config|
2
+
3
+ =begin
4
+ # These two settings work together to allow you to limit a spec run
5
+ # to individual examples or groups you care about by tagging them with
6
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
7
+ # get run.
8
+ config.filter_run :focus
9
+ config.run_all_when_everything_filtered = true
10
+
11
+ # Many RSpec users commonly either run the entire suite or an individual
12
+ # file, and it's useful to allow more verbose output when running an
13
+ # individual spec file.
14
+
15
+ # Print the 10 slowest examples and example groups at the
16
+ # end of the spec run, to help surface which specs are running
17
+ # particularly slow.
18
+ config.profile_examples = 10
19
+
20
+ # Run specs in random order to surface order dependencies. If you find an
21
+ # order dependency and want to debug it, you can fix the order by providing
22
+ # the seed, which is printed after each run.
23
+ # --seed 1234
24
+ config.order = :random
25
+
26
+ # Seed global randomization in this process using the `--seed` CLI option.
27
+ # Setting this allows you to use `--seed` to deterministically reproduce
28
+ # test failures related to randomization by passing the same `--seed` value
29
+ # as the one that triggered the failure.
30
+ Kernel.srand config.seed
31
+ =end
32
+
33
+ if config.files_to_run.one?
34
+ config.default_formatter = 'doc'
35
+ end
36
+
37
+ config.expect_with :rspec do |expectations|
38
+ expectations.syntax = :expect
39
+ end
40
+
41
+ config.mock_with :rspec do |mocks|
42
+ mocks.syntax = :expect
43
+
44
+ mocks.verify_partial_doubles = true
45
+ end
46
+ end
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Taskmeister::VERSION
9
9
  spec.authors = ["Ray Grasso"]
10
10
  spec.email = ["ray.grasso@gmail.com"]
11
- spec.summary = %q{A simple command line task manager.}
12
- spec.description = %q{Another command line task manager. You know, for the heck of it.}
11
+ spec.summary = %q{Another command line task manager.}
12
+ spec.description = %q{Another command line task manager. You know, because I'm special.}
13
13
  spec.homepage = "https://www.github.com/grassdog/taskmeister"
14
14
  spec.license = "MIT"
15
15
 
@@ -19,5 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.6"
22
- spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "rake", "~> 10"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "aruba", "~> 0.6"
25
+ spec.add_development_dependency "pry-byebug", "~> 1.3"
23
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taskmeister
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ray Grasso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-28 00:00:00.000000000 Z
11
+ date: 2014-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,17 +28,59 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '0'
47
+ version: '3.0'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - ">="
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aruba
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
39
81
  - !ruby/object:Gem::Version
40
- version: '0'
41
- description: Another command line task manager. You know, for the heck of it.
82
+ version: '1.3'
83
+ description: Another command line task manager. You know, because I'm special.
42
84
  email:
43
85
  - ray.grasso@gmail.com
44
86
  executables:
@@ -47,14 +89,34 @@ extensions: []
47
89
  extra_rdoc_files: []
48
90
  files:
49
91
  - ".gitignore"
92
+ - ".rspec"
50
93
  - ".ruby-version"
51
94
  - Gemfile
52
95
  - LICENSE.txt
53
96
  - README.md
54
97
  - Rakefile
55
98
  - bin/taskmeister
99
+ - features/add.feature
100
+ - features/done.feature
101
+ - features/list.feature
102
+ - features/project_dir.feature
103
+ - features/replace.feature
104
+ - features/show.feature
105
+ - features/support/env.rb
56
106
  - lib/taskmeister.rb
107
+ - lib/taskmeister/cli/main.rb
108
+ - lib/taskmeister/cli/options.rb
109
+ - lib/taskmeister/cli/task_list_name.rb
110
+ - lib/taskmeister/task.rb
111
+ - lib/taskmeister/task_list.rb
112
+ - lib/taskmeister/task_list_reader.rb
113
+ - lib/taskmeister/task_list_writer.rb
57
114
  - lib/taskmeister/version.rb
115
+ - spec/lib/taskmeister/task_list_reader_spec.rb
116
+ - spec/lib/taskmeister/task_list_spec.rb
117
+ - spec/lib/taskmeister/task_list_writer_spec.rb
118
+ - spec/lib/taskmeister/task_spec.rb
119
+ - spec/spec_helper.rb
58
120
  - taskmeister.gemspec
59
121
  homepage: https://www.github.com/grassdog/taskmeister
60
122
  licenses:
@@ -79,5 +141,17 @@ rubyforge_project:
79
141
  rubygems_version: 2.2.2
80
142
  signing_key:
81
143
  specification_version: 4
82
- summary: A simple command line task manager.
83
- test_files: []
144
+ summary: Another command line task manager.
145
+ test_files:
146
+ - features/add.feature
147
+ - features/done.feature
148
+ - features/list.feature
149
+ - features/project_dir.feature
150
+ - features/replace.feature
151
+ - features/show.feature
152
+ - features/support/env.rb
153
+ - spec/lib/taskmeister/task_list_reader_spec.rb
154
+ - spec/lib/taskmeister/task_list_spec.rb
155
+ - spec/lib/taskmeister/task_list_writer_spec.rb
156
+ - spec/lib/taskmeister/task_spec.rb
157
+ - spec/spec_helper.rb