medo 0.1.1 → 0.1.3
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.
- data/README.md +3 -0
- data/VERSION +1 -1
- data/bin/medo +3 -52
- data/features/add_task.feature +1 -1
- data/features/edit_task.feature +14 -0
- data/features/{add_notes.feature → notes.feature} +16 -7
- data/features/step_definitions/generic_steps.rb +4 -0
- data/features/support/env.rb +1 -1
- data/lib/medo.rb +1 -2
- data/lib/medo/cli.rb +48 -0
- data/lib/medo/cli_support.rb +68 -0
- data/lib/medo/commands/clear.rb +1 -3
- data/lib/medo/commands/delete.rb +6 -7
- data/lib/medo/commands/done.rb +6 -7
- data/lib/medo/commands/edit.rb +16 -0
- data/lib/medo/commands/list.rb +7 -6
- data/lib/medo/commands/new.rb +5 -8
- data/lib/medo/commands/note.rb +21 -10
- data/lib/medo/commands/reset.rb +12 -0
- data/lib/medo/commands/show.rb +8 -9
- data/lib/medo/notes.rb +39 -0
- data/lib/medo/task.rb +37 -7
- data/lib/medo/task_reader.rb +2 -0
- data/lib/medo/task_writer.rb +2 -0
- data/lib/medo/text_task_writer/decorators/numbers_decorator.rb +13 -3
- data/spec/lib/cli_spec.rb +6 -0
- data/spec/lib/cli_support_spec.rb +79 -0
- data/spec/lib/json_task_reader_spec.rb +12 -12
- data/spec/lib/notes_spec.rb +48 -0
- data/spec/lib/task_spec.rb +51 -16
- data/spec/support/task_stubs_spec_helper.rb +14 -9
- metadata +119 -122
data/lib/medo/commands/show.rb
CHANGED
@@ -2,17 +2,16 @@ require 'medo/text_task_writer'
|
|
2
2
|
|
3
3
|
desc "Show particular note"
|
4
4
|
command [:show, :cat] do |c|
|
5
|
-
c.
|
6
|
-
|
5
|
+
c.desc "Number of task to show"
|
6
|
+
c.flag [:n, :number]
|
7
|
+
c.default_value 1
|
7
8
|
|
9
|
+
c.action do |global_options, options, args|
|
8
10
|
include TextTaskWriter::Decorators
|
9
|
-
writer =
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
task, number = choose_task(args, tasks)
|
15
|
-
writer.add_tasks([task])
|
11
|
+
writer = TextTaskWriter.new
|
12
|
+
colorize { ColorsDecorator.decorate(writer) }
|
13
|
+
task, number = choose_task
|
14
|
+
writer.add_task(task)
|
16
15
|
writer.write
|
17
16
|
end
|
18
17
|
end
|
data/lib/medo/notes.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Medo
|
4
|
+
class Notes
|
5
|
+
include Comparable
|
6
|
+
include Enumerable
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@notes, :<=>, :empty?
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
@notes = args.flatten.map { |n| n.to_s.strip }.reject(&:empty?).join("\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(other)
|
16
|
+
@notes << "\n#{other}"
|
17
|
+
@notes.lstrip!
|
18
|
+
end
|
19
|
+
|
20
|
+
def each
|
21
|
+
@notes.each_line { |line| yield line.chomp }
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
return true if other.equal? self
|
26
|
+
other.to_s == @notes
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize_copy(source)
|
30
|
+
super
|
31
|
+
@notes = @notes.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@notes
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
data/lib/medo/task.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'medo/notes'
|
2
|
+
|
1
3
|
module Medo
|
2
4
|
class Task
|
3
5
|
include Comparable
|
@@ -20,11 +22,10 @@ module Medo
|
|
20
22
|
self.clock = Time
|
21
23
|
|
22
24
|
def initialize(description, options = {})
|
23
|
-
|
24
|
-
raise ArgumentError, "No description given!" if @description.empty?
|
25
|
+
self.description = description
|
25
26
|
@created_at = clock.now
|
26
27
|
@completed_at = nil
|
27
|
-
|
28
|
+
self.notes = options["notes"] || options[:notes]
|
28
29
|
end
|
29
30
|
|
30
31
|
def <=>(other)
|
@@ -40,11 +41,44 @@ module Medo
|
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
44
|
+
def ==(other)
|
45
|
+
return true if other.equal? self
|
46
|
+
return false unless other.kind_of?(self.class)
|
47
|
+
self.description == other.description &&
|
48
|
+
self.created_at == other.created_at &&
|
49
|
+
self.done? == other.done? &&
|
50
|
+
self.completed_at == other.completed_at &&
|
51
|
+
self.notes == other.notes
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize_copy(source)
|
55
|
+
super
|
56
|
+
@description = @description.dup
|
57
|
+
@created_at = @created_at.dup
|
58
|
+
@completed_at = @completed_at.dup if @completed_at
|
59
|
+
@notes = @notes.dup
|
60
|
+
end
|
61
|
+
|
62
|
+
def description=(desc)
|
63
|
+
description = desc.to_s.strip
|
64
|
+
raise ArgumentError, "No description given!" if description.empty?
|
65
|
+
@description = description
|
66
|
+
end
|
67
|
+
|
68
|
+
def notes=(*args)
|
69
|
+
@notes = Notes.new(*args)
|
70
|
+
end
|
71
|
+
|
43
72
|
def done
|
44
73
|
@completed_at = clock.now
|
45
74
|
@done = true
|
46
75
|
end
|
47
76
|
|
77
|
+
def reset
|
78
|
+
@completed_at = nil
|
79
|
+
@done = false
|
80
|
+
end
|
81
|
+
|
48
82
|
def done?
|
49
83
|
!!@done
|
50
84
|
end
|
@@ -54,10 +88,6 @@ module Medo
|
|
54
88
|
def clock
|
55
89
|
self.class.clock
|
56
90
|
end
|
57
|
-
|
58
|
-
def parse_notes(notes)
|
59
|
-
Array(notes).map { |n| n.to_s.strip }.reject(&:empty?)
|
60
|
-
end
|
61
91
|
end
|
62
92
|
end
|
63
93
|
|
data/lib/medo/task_reader.rb
CHANGED
data/lib/medo/task_writer.rb
CHANGED
@@ -6,24 +6,34 @@ module Medo
|
|
6
6
|
module NumbersDecorator
|
7
7
|
extend Support::Decorator
|
8
8
|
|
9
|
+
after_decorate do |options|
|
10
|
+
@num_options = options
|
11
|
+
end
|
12
|
+
|
9
13
|
def present_tasks(tasks)
|
10
14
|
max_tasks_count = [active_tasks.count, completed_tasks.count].max
|
11
15
|
super.each_with_index.map do |t, i|
|
12
|
-
TaskNumbers.decorate(t, i + 1, max_tasks_count)
|
16
|
+
TaskNumbers.decorate(t, i + 1, max_tasks_count, @num_options)
|
13
17
|
end
|
14
18
|
end
|
15
19
|
|
16
20
|
module TaskNumbers
|
17
21
|
extend Support::Decorator
|
18
22
|
|
19
|
-
after_decorate do |number, total|
|
23
|
+
after_decorate do |number, total, options|
|
20
24
|
@number = number
|
21
25
|
@number_length = total.to_s.size
|
26
|
+
@num_options = options || {}
|
22
27
|
end
|
23
28
|
|
24
29
|
def number
|
25
30
|
format = "%-#{@number_length + 1}s"
|
26
|
-
format % (
|
31
|
+
format % (number? ? "" : "#@number.")
|
32
|
+
end
|
33
|
+
|
34
|
+
def number?
|
35
|
+
@num_options[:done] && @task.done? ||
|
36
|
+
@num_options[:pending] && !@task.done?
|
27
37
|
end
|
28
38
|
|
29
39
|
def done
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'medo/cli_support'
|
3
|
+
|
4
|
+
describe "CLISupport" do
|
5
|
+
class CLISupportContext
|
6
|
+
include Medo::CLISupport
|
7
|
+
|
8
|
+
def storage
|
9
|
+
@storage ||= RSpec::Mocks::Mock.new(:Storage)
|
10
|
+
end
|
11
|
+
|
12
|
+
def global_options
|
13
|
+
@global_options ||= {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
# def load_commands
|
17
|
+
# def choose_task(select_options = {})
|
18
|
+
# def get_input
|
19
|
+
|
20
|
+
let(:tasks) { ["foo", "bar"] }
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
@context = CLISupportContext.new
|
24
|
+
@context.storage.stub(:read => tasks)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#tasks" do
|
28
|
+
it "should read and memoize tasks" do
|
29
|
+
@context.tasks.should == tasks
|
30
|
+
@context.tasks.object_id.should == tasks.object_id
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#tasks_changed" do
|
35
|
+
it "should react to changess in tasks collection" do
|
36
|
+
@context.tasks
|
37
|
+
expect do
|
38
|
+
tasks.last.upcase!
|
39
|
+
end.to change { @context.tasks_changed? }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#colorize" do
|
44
|
+
let(:touch) { mock }
|
45
|
+
|
46
|
+
it "should yield if color mode enabled" do
|
47
|
+
@context.global_options[:"no-color"] = true #color
|
48
|
+
touch.should_receive(:touched)
|
49
|
+
@context.colorize { touch.touched }
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should not yield if color mode disabled" do
|
53
|
+
@context.global_options[:"no-color"] = false #no color
|
54
|
+
touch.should_not_receive(:touched)
|
55
|
+
@context.colorize { touch.touched }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#committing tasks" do
|
60
|
+
it "should write and commit tasks if changed" do
|
61
|
+
@context.storage.should_receive(:write).with(tasks)
|
62
|
+
@context.storage.should_receive(:commit)
|
63
|
+
@context.tasks
|
64
|
+
@context.committing_tasks do
|
65
|
+
tasks.last.upcase!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should not write tasks if not changed" do
|
70
|
+
@context.storage.should_not_receive(:write)
|
71
|
+
@context.storage.should_not_receive(:commit)
|
72
|
+
@context.tasks
|
73
|
+
@context.committing_tasks { }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
|
@@ -3,24 +3,24 @@ require 'support/task_stubs_spec_helper'
|
|
3
3
|
require 'medo/json_task_reader'
|
4
4
|
require 'stringio'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
describe JsonTaskReader do
|
6
|
+
module Medo
|
9
7
|
unless defined? Task
|
10
8
|
class Task; end
|
11
9
|
end
|
12
10
|
|
13
|
-
|
11
|
+
describe JsonTaskReader do
|
12
|
+
include TaskStubsSpecHelper
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
it "should read tasks" do
|
15
|
+
fake_input = StringIO.new '[{"done":false,"description":"Buy Milk","created_at":"2012-01-05 12:04:00",'\
|
16
|
+
'"completed_at":null,"notes":""},{"done":true,"description":"Buy Butter","created_at":"2012-01-05 15:30:00",'\
|
17
|
+
'"completed_at":"2012-01-05 16:30:00","notes":"Note 1\nNote 2"}]'
|
19
18
|
|
20
|
-
|
21
|
-
|
19
|
+
Task.should_receive(:from_attributes).with(pending_task_attributes).and_return(1)
|
20
|
+
Task.should_receive(:from_attributes).with(completed_task_attributes).and_return(2)
|
22
21
|
|
23
|
-
|
24
|
-
|
22
|
+
JsonTaskReader.new(fake_input).read.should == [1, 2]
|
23
|
+
end
|
24
|
+
end
|
25
25
|
end
|
26
26
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'medo/notes'
|
3
|
+
|
4
|
+
module Medo
|
5
|
+
describe Notes do
|
6
|
+
it "should be create from various arguments" do
|
7
|
+
Notes.new(["1", "2"]).should == "1\n2"
|
8
|
+
Notes.new(0).should == "0"
|
9
|
+
Notes.new([0, nil]).should == "0"
|
10
|
+
Notes.new("note").should == "note"
|
11
|
+
Notes.new.should == ""
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should allow appending notes" do
|
15
|
+
notes = Notes.new
|
16
|
+
notes << "My Note"
|
17
|
+
notes.should == "My Note"
|
18
|
+
notes << "FooBar"
|
19
|
+
notes.should == "My Note\nFooBar"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be coerced to string" do
|
23
|
+
Notes.new(["foo", "bar"]).to_s.should == "foo\nbar"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should respond to #empty?" do
|
27
|
+
Notes.new.should be_empty
|
28
|
+
Notes.new("foo").should_not be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should be enumerable" do
|
32
|
+
expect { |b| Notes.new("foo\nbar").each(&b) }.to yield_successive_args("foo", "bar")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should test for equality" do
|
36
|
+
Notes.new("foo").should == Notes.new("foo")
|
37
|
+
Notes.new("foo").should_not == Notes.new("foo\nbar")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be duplicable" do
|
41
|
+
n1 = Notes.new("foo")
|
42
|
+
n2 = n1.dup
|
43
|
+
n1 << "bar"
|
44
|
+
n2.should == "foo"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
data/spec/lib/task_spec.rb
CHANGED
@@ -6,15 +6,11 @@ describe Medo::Task do
|
|
6
6
|
Medo::Task.new("description").description.should == "description"
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
proc { Medo::Task.new("") }.should raise_error(ArgumentError)
|
11
|
-
proc { Medo::Task.new(" ") }.should raise_error(ArgumentError)
|
12
|
-
end
|
13
|
-
|
9
|
+
Notes = Class.new unless defined? Notes
|
14
10
|
it "should allow notes to be set upon creation" do
|
15
|
-
|
16
|
-
|
17
|
-
Medo::Task.new("description", :notes => [
|
11
|
+
args = anything
|
12
|
+
Notes.should_receive(:new).with(args)
|
13
|
+
Medo::Task.new("description", :notes => ["1", "2"])
|
18
14
|
end
|
19
15
|
|
20
16
|
it "should assign creation time upon instantiation" do
|
@@ -24,12 +20,6 @@ describe Medo::Task do
|
|
24
20
|
end
|
25
21
|
end
|
26
22
|
|
27
|
-
it "should allow assigning notes to the task" do
|
28
|
-
task = Medo::Task.new("description")
|
29
|
-
task.notes << "My Note"
|
30
|
-
task.notes.should include("My Note")
|
31
|
-
end
|
32
|
-
|
33
23
|
it "should not allow comparison with shit" do
|
34
24
|
task1 = Medo::Task.new("asdfsd")
|
35
25
|
proc { task1 <=> :foo }.should raise_error(ArgumentError)
|
@@ -68,17 +58,24 @@ describe Medo::Task do
|
|
68
58
|
task.should be_done
|
69
59
|
end
|
70
60
|
|
61
|
+
it "should reset done state if #reset called" do
|
62
|
+
task = Medo::Task.new("Buy milk")
|
63
|
+
task.done
|
64
|
+
task.reset
|
65
|
+
task.should_not be_done
|
66
|
+
end
|
67
|
+
|
71
68
|
describe ".from_attributes" do
|
72
69
|
it "should allow all attributes to be set from hash" do
|
73
70
|
created_at = Time.now
|
74
71
|
completed_at = Time.now
|
75
72
|
task = Medo::Task.from_attributes("description" => "d",
|
76
|
-
"notes" =>
|
73
|
+
"notes" => "n",
|
77
74
|
"done" => true,
|
78
75
|
"completed_at" => completed_at,
|
79
76
|
"created_at" => created_at)
|
80
77
|
task.description.should == "d"
|
81
|
-
task.notes.should ==
|
78
|
+
task.notes.should == "n"
|
82
79
|
task.completed_at.should == completed_at
|
83
80
|
task.created_at.should == created_at
|
84
81
|
task.should be_done
|
@@ -92,6 +89,44 @@ describe Medo::Task do
|
|
92
89
|
end
|
93
90
|
end
|
94
91
|
|
92
|
+
it "should not be equal if notes differs" do
|
93
|
+
c = Time.now
|
94
|
+
t1 = Medo::Task.from_attributes("description" => "d", "created_at" => c, "notes" => "foo")
|
95
|
+
t2 = Medo::Task.from_attributes("description" => "d", "created_at" => c, "notes" => "foo")
|
96
|
+
t1.should == t2
|
97
|
+
t1.notes << "bar"
|
98
|
+
t1.should_not == t2
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should duplicate with children" do
|
102
|
+
t1 = Medo::Task.new("description", "notes" => "my note")
|
103
|
+
t2 = t1.dup
|
104
|
+
t1.description.should_not equal(t2.description)
|
105
|
+
t1.notes.should_not equal(t2.notes)
|
106
|
+
end
|
107
|
+
|
108
|
+
context "#description=" do
|
109
|
+
let(:task) { Medo::Task.new("description") }
|
110
|
+
|
111
|
+
it "should allow settings description" do
|
112
|
+
task.description = "foo"
|
113
|
+
task.description.should == "foo"
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should raise if description is bad" do
|
117
|
+
expect do
|
118
|
+
task.description = nil
|
119
|
+
end.to raise_error ArgumentError
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Notes = Class.new unless defined? Notes
|
124
|
+
it "should create a Notes instance on #notes=" do
|
125
|
+
t = Medo::Task.new("foo")
|
126
|
+
Notes.should_receive(:new).with("bar")
|
127
|
+
t.notes = "bar"
|
128
|
+
end
|
129
|
+
|
95
130
|
def using_fake_clock(fake_clock)
|
96
131
|
Medo::Task.clock = fake_clock
|
97
132
|
yield
|