medo 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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/README.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/bin/medo
CHANGED
@@ -1,62 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require '
|
5
|
-
|
6
|
-
begin
|
7
|
-
require 'gli'
|
8
|
-
require 'medo'
|
9
|
-
require 'medo/support'
|
10
|
-
require 'medo/file_task_storage'
|
11
|
-
rescue LoadError => e #development
|
12
|
-
raise if $loaded
|
13
|
-
$:.unshift File.expand_path('../../lib', __FILE__) #load what we work on, even if gem installed
|
14
|
-
require 'bundler'
|
15
|
-
Bundler.setup(:default)
|
16
|
-
$loaded = true
|
17
|
-
retry
|
18
|
-
end
|
19
|
-
|
20
|
-
include GLI::App
|
21
|
-
include Medo
|
22
|
-
|
23
|
-
program_desc 'Simple CLI To-Do manager'
|
24
|
-
version VERSION
|
25
|
-
|
26
|
-
commands_from 'medo/commands'
|
27
|
-
|
28
|
-
default_command :list
|
29
|
-
|
30
|
-
desc "A file with tasks"
|
31
|
-
flag [:f, "tasks-file"], :default_value => File.join(ENV['HOME'], '.medo-tasks')
|
32
|
-
|
33
|
-
desc "Do not use colorful output"
|
34
|
-
switch "no-color", :negatable => false
|
4
|
+
require 'medo/cli'
|
35
5
|
|
36
6
|
Signal.trap("SIGINT") do
|
37
7
|
puts "Terminating"
|
38
8
|
exit 1
|
39
9
|
end
|
40
10
|
|
41
|
-
|
42
|
-
|
43
|
-
instance_eval do
|
44
|
-
eigen = class << self; self; end
|
45
|
-
eigen.send(:define_method, :storage) { the_storage }
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.choose_task(argv, tasks)
|
49
|
-
input = argv.shift
|
50
|
-
number = Integer(input) rescue
|
51
|
-
raise(ArgumentError, "Invalid task #: #{input}")
|
52
|
-
task = tasks.reject(&:done?).sort[number - 1] or raise RuntimeError,
|
53
|
-
"No such task!"
|
54
|
-
[task, number]
|
55
|
-
end
|
56
|
-
|
57
|
-
cmd.call
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
run(ARGV)
|
11
|
+
app = Medo::CLI
|
12
|
+
app.run(ARGV)
|
62
13
|
|
data/features/add_task.feature
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: Edit todo
|
2
|
+
In order to be able to correct a mistake
|
3
|
+
As a user
|
4
|
+
I want to be able to edit task description
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given there's no file "/tmp/test-medo-tasks"
|
8
|
+
|
9
|
+
Scenario: Add todo
|
10
|
+
When I successfully run `medo --tasks-file=/tmp/test-medo-tasks new Hello World`
|
11
|
+
When I successfully run `medo --tasks-file=/tmp/test-medo-tasks edit Goodbye World`
|
12
|
+
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks ls`
|
13
|
+
Then the output should contain "Goodbye World"
|
14
|
+
And the output should not contain "Hello World"
|
@@ -1,18 +1,27 @@
|
|
1
|
-
Feature:
|
1
|
+
Feature: Manage task notes
|
2
2
|
In order to be able to sketch some ideas
|
3
3
|
As a user
|
4
|
-
I want to add notes to my tasks
|
4
|
+
I want to add and edit notes to my tasks
|
5
5
|
|
6
6
|
Background:
|
7
7
|
Given there's no file "/tmp/test-medo-tasks"
|
8
8
|
|
9
|
-
Scenario: Add todo
|
9
|
+
Scenario: Add todo note
|
10
10
|
When I successfully run `medo --tasks-file=/tmp/test-medo-tasks new Hello World`
|
11
11
|
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks new Goodbye Windows`
|
12
|
-
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks note 1 "Trash the PC"`
|
13
|
-
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks note 2 The Note`
|
14
|
-
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks show
|
12
|
+
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks note add -n 1 "Trash the PC"`
|
13
|
+
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks note add -n 2 The Note`
|
14
|
+
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks show`
|
15
15
|
Then the output should contain "Trash the PC"
|
16
16
|
And the output should not contain "The Note"
|
17
|
-
When I successfully run `medo --tasks-file=/tmp/test-medo-tasks show 2`
|
17
|
+
When I successfully run `medo --tasks-file=/tmp/test-medo-tasks show -n 2`
|
18
18
|
Then the output should contain "The Note"
|
19
|
+
|
20
|
+
Scenario: Edit todo note
|
21
|
+
When I successfully run `medo --tasks-file=/tmp/test-medo-tasks new Hello World`
|
22
|
+
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks note add "Trash the PC"`
|
23
|
+
When I successfully run `medo --tasks-file=/tmp/test-medo-tasks note edit boom`
|
24
|
+
And I successfully run `medo --tasks-file=/tmp/test-medo-tasks show`
|
25
|
+
Then the output should not contain "Trash the PC"
|
26
|
+
And the output should contain "boom"
|
27
|
+
|
data/features/support/env.rb
CHANGED
@@ -5,7 +5,7 @@ require 'fileutils'
|
|
5
5
|
|
6
6
|
ENV['PATH'] = "#{File.expand_path('../../../bin', __FILE__)}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
7
7
|
ENV['GLI_DEBUG'] = "true"
|
8
|
-
|
8
|
+
ENV['RUBYLIB'] = File.expand_path('../../../lib', __FILE__)
|
9
9
|
After do
|
10
10
|
if defined? @tasks_file_path and File.exist?(@tasks_file_path)
|
11
11
|
FileUtils.rm(@tasks_file_path)
|
data/lib/medo.rb
CHANGED
data/lib/medo/cli.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
require 'gli'
|
6
|
+
require 'medo'
|
7
|
+
require 'medo/support'
|
8
|
+
require 'medo/file_task_storage'
|
9
|
+
require 'medo/cli_support'
|
10
|
+
|
11
|
+
module Medo
|
12
|
+
class CLI
|
13
|
+
extend GLI::App
|
14
|
+
extend Medo
|
15
|
+
extend CLISupport
|
16
|
+
|
17
|
+
program_desc 'Simple CLI To-Do manager'
|
18
|
+
version VERSION
|
19
|
+
|
20
|
+
load_commands
|
21
|
+
default_command :list
|
22
|
+
|
23
|
+
desc "A file with tasks"
|
24
|
+
flag [:f, "tasks-file"], :default_value => File.join(ENV['HOME'], '.medo-tasks')
|
25
|
+
|
26
|
+
desc "Do not use colorful output"
|
27
|
+
switch "no-color", :negatable => false
|
28
|
+
|
29
|
+
around do |global_options, command, options, arguments, cmd|
|
30
|
+
FileTaskStorage.using_storage(global_options.fetch(:"tasks-file")) do |storage|
|
31
|
+
instance_eval do
|
32
|
+
eigen = class << self; self; end
|
33
|
+
{
|
34
|
+
:storage => storage,
|
35
|
+
:global_options => global_options,
|
36
|
+
:options => options,
|
37
|
+
:arguments => arguments
|
38
|
+
}.each do |method, value|
|
39
|
+
eigen.send(:define_method, method) { value }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
cmd.call
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Medo
|
2
|
+
module CLISupport
|
3
|
+
def load_commands
|
4
|
+
Dir.glob(File.expand_path('../commands/*', __FILE__)).each do |f|
|
5
|
+
contents = File.read(f)
|
6
|
+
class_eval contents, f, 1
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def tasks
|
11
|
+
@tasks ||= storage.read
|
12
|
+
@original_tasks ||= @tasks.map(&:dup)
|
13
|
+
@tasks
|
14
|
+
end
|
15
|
+
|
16
|
+
def tasks_changed?
|
17
|
+
defined? @tasks and @tasks != @original_tasks
|
18
|
+
end
|
19
|
+
|
20
|
+
def committing_tasks
|
21
|
+
yield
|
22
|
+
|
23
|
+
if tasks_changed?
|
24
|
+
storage.write(tasks)
|
25
|
+
storage.commit
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def colorize
|
30
|
+
yield unless global_options[:"no-color"] == false
|
31
|
+
end
|
32
|
+
|
33
|
+
def choose_task(select_options = {})
|
34
|
+
task_number = Integer(options[:number] || 1) rescue
|
35
|
+
raise(ArgumentError, "Invalid task #: #{task_number}")
|
36
|
+
task = tasks.reject { |t| select_options[:done] ^ t.done? }.sort[task_number - 1] or
|
37
|
+
raise(RuntimeError, "No such task!")
|
38
|
+
[task, task_number]
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_input
|
42
|
+
process_input
|
43
|
+
end
|
44
|
+
|
45
|
+
def edit_input(value)
|
46
|
+
process_input do |path|
|
47
|
+
File.open(path, "w") { |f| f.write(value) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def process_input
|
52
|
+
result = nil
|
53
|
+
if options[:editor]
|
54
|
+
path = File.join(Dir::Tmpname.tmpdir, "taketo-input-#{Time.now.to_i}")
|
55
|
+
yield path if block_given?
|
56
|
+
status = system("$EDITOR #{path}")
|
57
|
+
if status && File.exists?(path)
|
58
|
+
result = File.read(path)
|
59
|
+
FileUtils.rm(path)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
result = arguments.join(" ").strip
|
63
|
+
end
|
64
|
+
result.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
data/lib/medo/commands/clear.rb
CHANGED
data/lib/medo/commands/delete.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
desc "Delete a todo"
|
2
2
|
command [:delete, :rm] do |c|
|
3
|
-
c.
|
4
|
-
|
5
|
-
|
6
|
-
task, number = choose_task(args, tasks)
|
7
|
-
tasks -= [task]
|
3
|
+
c.desc "Number of the task to delete"
|
4
|
+
c.flag [:n, :number]
|
5
|
+
c.default_value 1
|
8
6
|
|
9
|
-
|
10
|
-
|
7
|
+
c.action do |global_options, options, args|
|
8
|
+
task, number = choose_task
|
9
|
+
committing_tasks { tasks.delete(task) }
|
11
10
|
puts "Task #{number} removed"
|
12
11
|
end
|
13
12
|
end
|
data/lib/medo/commands/done.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
desc "Mark todo as done"
|
2
2
|
command :done do |c|
|
3
|
-
c.
|
4
|
-
|
5
|
-
|
6
|
-
task, number = choose_task(args, tasks)
|
7
|
-
task.done
|
3
|
+
c.desc "Number of the task to mark as done"
|
4
|
+
c.flag [:n, :number]
|
5
|
+
c.default_value 1
|
8
6
|
|
9
|
-
|
10
|
-
|
7
|
+
c.action do |global_options, options, args|
|
8
|
+
task, number = choose_task
|
9
|
+
committing_tasks { task.done }
|
11
10
|
puts "Task #{number} done"
|
12
11
|
end
|
13
12
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
desc "Edit a todo"
|
2
|
+
command :edit do |c|
|
3
|
+
c.desc "Use EDITOR"
|
4
|
+
c.switch [:e, :editor]
|
5
|
+
|
6
|
+
c.desc "Number of the task to edit"
|
7
|
+
c.flag [:n, :number]
|
8
|
+
c.default_value 1
|
9
|
+
|
10
|
+
c.action do |global_options, options, args|
|
11
|
+
task, number = choose_task
|
12
|
+
task_description = get_input
|
13
|
+
committing_tasks { task.description = task_description }
|
14
|
+
puts "Task #{number} edited"
|
15
|
+
end
|
16
|
+
end
|
data/lib/medo/commands/list.rb
CHANGED
@@ -2,15 +2,16 @@ require 'medo/text_task_writer'
|
|
2
2
|
|
3
3
|
desc "List all todos"
|
4
4
|
command [:list, :ls] do |c|
|
5
|
-
c.
|
6
|
-
|
5
|
+
c.desc "Number only done tasks"
|
6
|
+
c.switch ["number-done"], :negatable => false
|
7
7
|
|
8
|
+
c.action do |global_options, options, args|
|
8
9
|
include TextTaskWriter::Decorators
|
9
|
-
writer = NumbersDecorator.decorate(TextTaskWriter.new
|
10
|
-
|
11
|
-
|
12
|
-
ColorsDecorator.decorate(writer) unless global_options[:"no-color"] == false
|
10
|
+
writer = NumbersDecorator.decorate(TextTaskWriter.new,
|
11
|
+
:done => options[:"number-done"] == false)
|
12
|
+
colorize { ColorsDecorator.decorate(writer) }
|
13
13
|
writer.add_tasks(tasks)
|
14
14
|
writer.write
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
data/lib/medo/commands/new.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
desc "Create a todo"
|
2
2
|
command :new do |c|
|
3
|
-
c.
|
4
|
-
|
3
|
+
c.desc "Use EDITOR"
|
4
|
+
c.switch [:e, :editor]
|
5
5
|
|
6
|
-
|
6
|
+
c.action do |global_options, options, args|
|
7
|
+
task_description = get_input
|
7
8
|
task, number = Task.new(task_description)
|
8
|
-
|
9
|
-
tasks << task
|
10
|
-
|
11
|
-
storage.write(tasks)
|
12
|
-
storage.commit
|
9
|
+
committing_tasks { tasks << task }
|
13
10
|
puts "Task added"
|
14
11
|
end
|
15
12
|
end
|
data/lib/medo/commands/note.rb
CHANGED
@@ -1,18 +1,29 @@
|
|
1
1
|
desc "Add note to a todo"
|
2
2
|
command :note do |c|
|
3
|
-
c.
|
4
|
-
|
3
|
+
c.desc "Number of the task to add note to"
|
4
|
+
c.flag [:n, :number]
|
5
|
+
c.default_value 1
|
5
6
|
|
6
|
-
|
7
|
+
c.desc "Use EDITOR"
|
8
|
+
c.switch [:e, :editor]
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
c.command :add do |ca|
|
11
|
+
ca.action do |global_options, options, args|
|
12
|
+
task, number = choose_task
|
13
|
+
note = get_input
|
14
|
+
raise ArgumentError, "No note given" if note.empty?
|
15
|
+
committing_tasks { task.notes << note }
|
16
|
+
puts "Note for task #{number} added"
|
17
|
+
end
|
18
|
+
end
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
|
20
|
+
c.command :edit do |ce|
|
21
|
+
ce.action do |global_options, options, args|
|
22
|
+
task, number = choose_task
|
23
|
+
note = edit_input(task.notes)
|
24
|
+
committing_tasks { task.notes = note }
|
25
|
+
puts "Note for task #{number} edited"
|
26
|
+
end
|
16
27
|
end
|
17
28
|
end
|
18
29
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
desc "Reset task"
|
2
|
+
command :reset do |c|
|
3
|
+
c.desc "Number of the task to mark reset"
|
4
|
+
c.flag [:n, :number]
|
5
|
+
c.default_value 1
|
6
|
+
|
7
|
+
c.action do |global_options, options, args|
|
8
|
+
task, number = choose_task(:done => true)
|
9
|
+
committing_tasks { task.reset }
|
10
|
+
puts "Task #{number} was reset"
|
11
|
+
end
|
12
|
+
end
|