markdo 0.1.12.alpha → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +15 -9
- data/Dockerfile +21 -7
- data/Guardfile +11 -0
- data/README.md +12 -7
- data/Rakefile +11 -7
- data/bin/markdo +1 -1
- data/docker-compose.yml +9 -0
- data/lib/markdo/cli.rb +23 -55
- data/lib/markdo/command_support.rb +14 -0
- data/lib/markdo/commands.rb +16 -0
- data/lib/markdo/{add_command.rb → commands/add_command.rb} +2 -6
- data/lib/markdo/commands/command.rb +33 -0
- data/lib/markdo/{edit_command.rb → commands/edit_command.rb} +2 -2
- data/lib/markdo/commands/forecast_command.rb +41 -0
- data/lib/markdo/{help_command.rb → commands/help_command.rb} +7 -8
- data/lib/markdo/commands/ics_command.rb +15 -0
- data/lib/markdo/commands/inbox_command.rb +11 -0
- data/lib/markdo/commands/overdue_command.rb +11 -0
- data/lib/markdo/commands/overview_command.rb +16 -0
- data/lib/markdo/commands/process_command.rb +91 -0
- data/lib/markdo/commands/query_command.rb +14 -0
- data/lib/markdo/commands/star_command.rb +11 -0
- data/lib/markdo/commands/summary_command.rb +25 -0
- data/lib/markdo/commands/tag_command.rb +11 -0
- data/lib/markdo/commands/today_command.rb +12 -0
- data/lib/markdo/commands/tomorrow_command.rb +12 -0
- data/lib/markdo/{version_command.rb → commands/version_command.rb} +1 -1
- data/lib/markdo/commands/week_command.rb +16 -0
- data/lib/markdo/data_source.rb +27 -0
- data/lib/markdo/ics_exporter.rb +65 -0
- data/lib/markdo/models/task.rb +46 -0
- data/lib/markdo/models/task_attribute.rb +22 -0
- data/lib/markdo/models/task_collection.rb +75 -0
- data/lib/markdo/version.rb +1 -1
- data/markdo.gemspec +6 -0
- data/script/build +9 -0
- data/spec/fixtures/add_command/Inbox.md +0 -0
- data/spec/fixtures/date_commands/Inbox.md +6 -0
- data/spec/fixtures/date_commands/Sprint.md +6 -0
- data/spec/fixtures/ics_command/Inbox.md +0 -0
- data/{test/fixtures/ics_command.md → spec/fixtures/ics_command/Sprint.md} +0 -0
- data/spec/fixtures/inbox_command/Inbox.md +2 -0
- data/spec/fixtures/inbox_command/Sprint.md +2 -0
- data/spec/fixtures/process_command/Backlog.md +0 -0
- data/spec/fixtures/process_command/Inbox.md +0 -0
- data/spec/fixtures/process_command/Maybe.md +0 -0
- data/spec/fixtures/process_command/Sprint.md +0 -0
- data/spec/fixtures/query_command/Inbox.md +4 -0
- data/spec/fixtures/query_command/Sprint.md +4 -0
- data/spec/fixtures/tag_command/Inbox.md +2 -0
- data/spec/fixtures/tag_command/Sprint.md +2 -0
- data/spec/lib/cli_spec.rb +79 -0
- data/spec/lib/commands/add_command_spec.rb +70 -0
- data/spec/lib/commands/edit_command_spec.rb +32 -0
- data/spec/lib/commands/forecast_command_spec.rb +24 -0
- data/spec/lib/commands/ics_command_spec.rb +30 -0
- data/spec/lib/commands/inbox_command_spec.rb +22 -0
- data/spec/lib/commands/overdue_command_spec.rb +19 -0
- data/spec/lib/commands/overview_command_spec.rb +25 -0
- data/spec/lib/commands/process_command_spec.rb +178 -0
- data/spec/lib/commands/query_command_spec.rb +23 -0
- data/spec/lib/commands/star_command_spec.rb +19 -0
- data/spec/lib/commands/summary_command_spec.rb +23 -0
- data/spec/lib/commands/tag_command_spec.rb +21 -0
- data/spec/lib/commands/today_command_spec.rb +19 -0
- data/spec/lib/commands/tomorrow_command_spec.rb +19 -0
- data/spec/lib/commands/week_command_spec.rb +23 -0
- data/spec/lib/ics_exporter_spec.rb +59 -0
- data/spec/lib/models/task_attribute_spec.rb +85 -0
- data/spec/lib/models/task_collection_spec.rb +168 -0
- data/spec/lib/models/task_spec.rb +79 -0
- data/spec/spec_helper.rb +40 -0
- metadata +189 -42
- data/.ruby-version +0 -1
- data/lib/markdo/command.rb +0 -12
- data/lib/markdo/date_command.rb +0 -9
- data/lib/markdo/forecast_command.rb +0 -61
- data/lib/markdo/ics_command.rb +0 -73
- data/lib/markdo/inbox_command.rb +0 -15
- data/lib/markdo/overdue_command.rb +0 -47
- data/lib/markdo/overview_command.rb +0 -17
- data/lib/markdo/process_command.rb +0 -68
- data/lib/markdo/query_command.rb +0 -24
- data/lib/markdo/rss_command.rb +0 -67
- data/lib/markdo/star_command.rb +0 -9
- data/lib/markdo/summary_command.rb +0 -30
- data/lib/markdo/tag_command.rb +0 -9
- data/lib/markdo/today_command.rb +0 -10
- data/lib/markdo/tomorrow_command.rb +0 -10
- data/lib/markdo/week_command.rb +0 -36
- data/test/fixtures/inbox.md +0 -2
- data/test/fixtures/rss_command.md +0 -4
- data/test/ics_command_test.rb +0 -38
- data/test/inbox_command_test.rb +0 -19
- data/test/rss_command_test.rb +0 -39
- data/test/summary_command_test.rb +0 -19
- data/test/test_helper.rb +0 -3
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'markdo/commands/command'
|
2
|
+
require 'markdo/data_source'
|
3
|
+
require 'markdo/ics_exporter'
|
4
|
+
require 'markdo/models/task_collection'
|
5
|
+
|
6
|
+
module Markdo
|
7
|
+
class IcsCommand < Command
|
8
|
+
def run
|
9
|
+
ics_exporter = IcsExporter.new(task_collection)
|
10
|
+
ics = ics_exporter.to_ics
|
11
|
+
|
12
|
+
@stdout.puts(ics)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'markdo/commands/command'
|
2
|
+
|
3
|
+
module Markdo
|
4
|
+
class OverviewCommand < Command
|
5
|
+
def run
|
6
|
+
tasks = task_collection.overdue +
|
7
|
+
task_collection.starred +
|
8
|
+
task_collection.due_today +
|
9
|
+
task_collection.due_tomorrow
|
10
|
+
|
11
|
+
tasks.each do |task|
|
12
|
+
@stdout.puts(task.line)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'markdo/commands/command'
|
3
|
+
|
4
|
+
module Markdo
|
5
|
+
class ProcessCommand < Command
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
@lines_by_filename = Hash.new { [] }
|
9
|
+
@lines = File.readlines(data_source.inbox_path)
|
10
|
+
@line_index = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
catch :abort do
|
15
|
+
while has_lines?
|
16
|
+
line = current_line
|
17
|
+
choice = prompt(line)
|
18
|
+
|
19
|
+
case choice
|
20
|
+
when 'h'
|
21
|
+
show_help
|
22
|
+
when 'i'
|
23
|
+
move_line_to(@env['MARKDO_INBOX'], line)
|
24
|
+
when 's'
|
25
|
+
move_line_to('Sprint.md', line)
|
26
|
+
when 'b'
|
27
|
+
move_line_to('Backlog.md', line)
|
28
|
+
when 'm'
|
29
|
+
move_line_to('Maybe.md', line)
|
30
|
+
when 'a'
|
31
|
+
throw :abort
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
write_files
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def has_lines?
|
42
|
+
@line_index < @lines.length
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_line
|
46
|
+
@lines[@line_index]
|
47
|
+
end
|
48
|
+
|
49
|
+
def prompt(line)
|
50
|
+
@stdout.puts line
|
51
|
+
@stdout.print 'File [hisbma]? '
|
52
|
+
|
53
|
+
input_line = @stdin.gets
|
54
|
+
|
55
|
+
@stdout.puts
|
56
|
+
|
57
|
+
if input_line.nil?
|
58
|
+
throw :abort
|
59
|
+
else
|
60
|
+
input_line.chomp.downcase
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def move_line_to(filename, line)
|
65
|
+
@lines_by_filename[filename] <<= line
|
66
|
+
@line_index += 1
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_files
|
70
|
+
inbox_lines = @lines_by_filename.delete(@env['MARKDO_INBOX'])
|
71
|
+
File.write(data_source.inbox_path, inbox_lines ? inbox_lines.join : '')
|
72
|
+
|
73
|
+
@lines_by_filename.each do |filename, lines|
|
74
|
+
path = data_source.file_path(filename)
|
75
|
+
new_content = ["\n## Processed on #{@today.iso8601}\n\n"] << lines
|
76
|
+
|
77
|
+
File.open(path, 'a') do |file|
|
78
|
+
file.puts(new_content.join)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def show_help
|
84
|
+
@stdout.puts 'i - inbox (keep in inbox)'
|
85
|
+
@stdout.puts 's - sprint'
|
86
|
+
@stdout.puts 'b - backlog'
|
87
|
+
@stdout.puts 'm - maybe'
|
88
|
+
@stdout.puts 'a - abort; make no changes'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'markdo/commands/command'
|
2
|
+
|
3
|
+
module Markdo
|
4
|
+
class QueryCommand < Command
|
5
|
+
def run(string)
|
6
|
+
regexp = Regexp.new(string, Regexp::IGNORECASE)
|
7
|
+
tasks = task_collection.with_match(regexp).reject(&:complete?)
|
8
|
+
|
9
|
+
tasks.each do |task|
|
10
|
+
@stdout.puts(task.line)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'markdo/commands/command'
|
2
|
+
|
3
|
+
module Markdo
|
4
|
+
class SummaryCommand < Command
|
5
|
+
def run
|
6
|
+
print_count('Overdue', task_collection.overdue)
|
7
|
+
print_count('Starred', task_collection.with_tag('star'))
|
8
|
+
print_count('Today', task_collection.due_today)
|
9
|
+
print_count('Tomorrow', task_collection.due_tomorrow)
|
10
|
+
print_count('Soon', task_collection.due_soon)
|
11
|
+
|
12
|
+
print_count('Inbox', inbox_task_collection.all)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def print_count(name, tasks)
|
18
|
+
count = tasks.length
|
19
|
+
|
20
|
+
unless count.zero?
|
21
|
+
@stdout.puts "#{name}: #{count}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'markdo/commands/command'
|
3
|
+
|
4
|
+
module Markdo
|
5
|
+
class WeekCommand < Command
|
6
|
+
def run
|
7
|
+
tasks = task_collection.due_today +
|
8
|
+
task_collection.due_tomorrow +
|
9
|
+
task_collection.due_soon
|
10
|
+
|
11
|
+
tasks.each do |task|
|
12
|
+
@stdout.puts(task.line)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Markdo
|
2
|
+
class DataSource
|
3
|
+
def initialize(env)
|
4
|
+
@env = env
|
5
|
+
end
|
6
|
+
|
7
|
+
def lines_from_all
|
8
|
+
Dir.
|
9
|
+
glob("#{@env['MARKDO_ROOT']}/*.md").
|
10
|
+
sort.
|
11
|
+
map { |path| File.readlines(path, encoding: 'UTF-8') }.
|
12
|
+
flatten
|
13
|
+
end
|
14
|
+
|
15
|
+
def lines_from_inbox
|
16
|
+
File.readlines(inbox_path, encoding: 'UTF-8')
|
17
|
+
end
|
18
|
+
|
19
|
+
def inbox_path
|
20
|
+
file_path(@env['MARKDO_INBOX'])
|
21
|
+
end
|
22
|
+
|
23
|
+
def file_path(filename)
|
24
|
+
File.join(@env['MARKDO_ROOT'], filename)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Markdo
|
4
|
+
class IcsExporter
|
5
|
+
def initialize(task_collection)
|
6
|
+
@task_collection = task_collection
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_ics
|
10
|
+
buf = []
|
11
|
+
|
12
|
+
buf << 'BEGIN:VCALENDAR'
|
13
|
+
buf << 'VERSION:2.0'
|
14
|
+
buf << 'CALSCALE:GREGORIAN'
|
15
|
+
buf << 'METHOD:PUBLISH'
|
16
|
+
buf << 'X-WR-CALNAME:Markdo Due Dates'
|
17
|
+
buf << events.map { |event| event.to_ics }
|
18
|
+
buf << 'END:VCALENDAR'
|
19
|
+
|
20
|
+
buf.
|
21
|
+
flatten.
|
22
|
+
map { |line| "#{line}\n" }.
|
23
|
+
join
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def events
|
29
|
+
@task_collection.
|
30
|
+
with_attribute('due').
|
31
|
+
reject { |task| task.complete? }.
|
32
|
+
map { |task|
|
33
|
+
due_date = task.attributes['due'].date_value
|
34
|
+
summary = clean(task.body)
|
35
|
+
|
36
|
+
if due_date
|
37
|
+
Event.new(due_date, due_date, summary)
|
38
|
+
end
|
39
|
+
}.
|
40
|
+
compact
|
41
|
+
end
|
42
|
+
|
43
|
+
def clean(line)
|
44
|
+
line.sub(%r(@due\(.*\)\s), '')
|
45
|
+
end
|
46
|
+
|
47
|
+
class Event
|
48
|
+
def initialize(date_start, date_end, summary)
|
49
|
+
@date_start = date_start
|
50
|
+
@date_end = date_end
|
51
|
+
@summary = summary
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_ics
|
55
|
+
buf = []
|
56
|
+
buf << 'BEGIN:VEVENT'
|
57
|
+
buf << "DTSTART;VALUE=DATE:#{@date_start.strftime('%Y%m%d')}"
|
58
|
+
buf << "DTEND;VALUE=DATE:#{@date_end.strftime('%Y%m%d')}"
|
59
|
+
buf << "SUMMARY:#{@summary}"
|
60
|
+
buf << 'END:VEVENT'
|
61
|
+
buf.join("\n")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'markdo/models/task_attribute'
|
2
|
+
|
3
|
+
module Markdo
|
4
|
+
class Task
|
5
|
+
attr_reader :line
|
6
|
+
|
7
|
+
def initialize(line)
|
8
|
+
@line = line
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
other.line == line
|
13
|
+
end
|
14
|
+
|
15
|
+
def complete?
|
16
|
+
!!line.match(/\s*[-*] \[x\]\s+/)
|
17
|
+
end
|
18
|
+
|
19
|
+
def body
|
20
|
+
line.
|
21
|
+
sub(/\s*[-*] \[.\]\s+/, '').
|
22
|
+
sub(/\s*$/, '')
|
23
|
+
end
|
24
|
+
|
25
|
+
def tags
|
26
|
+
attributes.keys
|
27
|
+
end
|
28
|
+
|
29
|
+
def attributes
|
30
|
+
a = line.
|
31
|
+
scan(/\s@\S+/).
|
32
|
+
map { |s|
|
33
|
+
match_data = s.match(/@([a-zA-Z0-9]+)(\((.+)\))?/)
|
34
|
+
|
35
|
+
if match_data
|
36
|
+
key = match_data[1].downcase
|
37
|
+
value = match_data[3]
|
38
|
+
|
39
|
+
[key, TaskAttribute.new(key, value)]
|
40
|
+
end
|
41
|
+
}
|
42
|
+
|
43
|
+
Hash[a]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Markdo
|
4
|
+
class TaskAttribute
|
5
|
+
attr_reader :key, :value
|
6
|
+
|
7
|
+
def initialize(key, value)
|
8
|
+
@key = key
|
9
|
+
@value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def date_value
|
13
|
+
Date.parse(value.to_s)
|
14
|
+
rescue ArgumentError
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
other.key == key && other.value == value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'markdo/models/task'
|
3
|
+
|
4
|
+
module Markdo
|
5
|
+
class TaskCollection
|
6
|
+
def initialize(lines, today = Date.today)
|
7
|
+
@lines = lines
|
8
|
+
@today = today
|
9
|
+
end
|
10
|
+
|
11
|
+
def all
|
12
|
+
tasks
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_tag(tag)
|
16
|
+
tasks.select { |task| task.attributes[tag.downcase] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_attribute(tag)
|
20
|
+
with_tag(tag)
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_match(matcher)
|
24
|
+
tasks.select { |task| task.line.match(matcher) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def starred
|
28
|
+
with_tag('star')
|
29
|
+
end
|
30
|
+
|
31
|
+
def due_on(given_date)
|
32
|
+
with_date('due') { |date| date == given_date }
|
33
|
+
end
|
34
|
+
|
35
|
+
def due_between(begin_date, end_date)
|
36
|
+
with_date('due') { |date| date >= begin_date && date <= end_date }
|
37
|
+
end
|
38
|
+
|
39
|
+
def overdue
|
40
|
+
with_date('due') { |date| date < @today }
|
41
|
+
end
|
42
|
+
|
43
|
+
def due_today
|
44
|
+
due_on(@today)
|
45
|
+
end
|
46
|
+
|
47
|
+
def due_tomorrow
|
48
|
+
due_on(@today + 1)
|
49
|
+
end
|
50
|
+
|
51
|
+
def due_soon
|
52
|
+
due_between(@today + 2, @today + 7)
|
53
|
+
end
|
54
|
+
|
55
|
+
def deferred_until_today
|
56
|
+
with_date('defer') { |date| date <= @today }
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def with_date(attribute, &block)
|
62
|
+
with_attribute(attribute).
|
63
|
+
select { |task| block.call(task.attributes[attribute].date_value) }.
|
64
|
+
sort_by { |task| task.attributes[attribute].date_value }
|
65
|
+
end
|
66
|
+
|
67
|
+
def tasks
|
68
|
+
@tasks ||= parse
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse
|
72
|
+
@lines.map { |line| Task.new(line) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|