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.
@@ -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.action do |global_options, options, args|
6
- tasks = storage.read
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 = NumbersDecorator.decorate(TextTaskWriter.new)
10
-
11
- #waiting for fix of https://github.com/davetron5000/gli/pull/90
12
- ColorsDecorator.decorate(writer) unless global_options[:"no-color"] == false
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
@@ -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
+
@@ -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
- @description = description.to_s.strip
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
- @notes = parse_notes(options["notes"] || options[:notes])
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
 
@@ -1,3 +1,5 @@
1
+ require 'medo/task'
2
+
1
3
  module Medo
2
4
  class TaskReader
3
5
  def read
@@ -1,3 +1,5 @@
1
+ require 'medo/task'
2
+
1
3
  module Medo
2
4
  class TaskWriter
3
5
  def initialize
@@ -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 % (@task.done? ? "" : "#@number.")
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,6 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+ require 'medo/cli'
3
+
4
+ describe "CLI" do
5
+ end
6
+
@@ -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
- include Medo
7
-
8
- describe JsonTaskReader do
6
+ module Medo
9
7
  unless defined? Task
10
8
  class Task; end
11
9
  end
12
10
 
13
- include TaskStubsSpecHelper
11
+ describe JsonTaskReader do
12
+ include TaskStubsSpecHelper
14
13
 
15
- it "should read tasks" do
16
- fake_input = StringIO.new '[{"done":false,"description":"Buy Milk","created_at":"2012-01-05 12:04:00",'\
17
- '"completed_at":null,"notes":[]},{"done":true,"description":"Buy Butter","created_at":"2012-01-05 15:30:00",'\
18
- '"completed_at":"2012-01-05 16:30:00","notes":["Note 1","Note 2"]}]'
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
- Task.should_receive(:from_attributes).with(pending_task_attributes).and_return(1)
21
- Task.should_receive(:from_attributes).with(completed_task_attributes).and_return(2)
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
- JsonTaskReader.new(fake_input).read.should == [1, 2]
24
- end
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
+
@@ -6,15 +6,11 @@ describe Medo::Task do
6
6
  Medo::Task.new("description").description.should == "description"
7
7
  end
8
8
 
9
- it "should require description to be set" do
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
- Medo::Task.new("description", :notes => ["1", "2"]).notes.should == ["1", "2"]
16
- Medo::Task.new("description", :notes => 0).notes.should == ["0"]
17
- Medo::Task.new("description", :notes => [0, nil]).notes.should == ["0"]
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" => ["n"],
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 == ["n"]
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