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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +15 -9
  4. data/Dockerfile +21 -7
  5. data/Guardfile +11 -0
  6. data/README.md +12 -7
  7. data/Rakefile +11 -7
  8. data/bin/markdo +1 -1
  9. data/docker-compose.yml +9 -0
  10. data/lib/markdo/cli.rb +23 -55
  11. data/lib/markdo/command_support.rb +14 -0
  12. data/lib/markdo/commands.rb +16 -0
  13. data/lib/markdo/{add_command.rb → commands/add_command.rb} +2 -6
  14. data/lib/markdo/commands/command.rb +33 -0
  15. data/lib/markdo/{edit_command.rb → commands/edit_command.rb} +2 -2
  16. data/lib/markdo/commands/forecast_command.rb +41 -0
  17. data/lib/markdo/{help_command.rb → commands/help_command.rb} +7 -8
  18. data/lib/markdo/commands/ics_command.rb +15 -0
  19. data/lib/markdo/commands/inbox_command.rb +11 -0
  20. data/lib/markdo/commands/overdue_command.rb +11 -0
  21. data/lib/markdo/commands/overview_command.rb +16 -0
  22. data/lib/markdo/commands/process_command.rb +91 -0
  23. data/lib/markdo/commands/query_command.rb +14 -0
  24. data/lib/markdo/commands/star_command.rb +11 -0
  25. data/lib/markdo/commands/summary_command.rb +25 -0
  26. data/lib/markdo/commands/tag_command.rb +11 -0
  27. data/lib/markdo/commands/today_command.rb +12 -0
  28. data/lib/markdo/commands/tomorrow_command.rb +12 -0
  29. data/lib/markdo/{version_command.rb → commands/version_command.rb} +1 -1
  30. data/lib/markdo/commands/week_command.rb +16 -0
  31. data/lib/markdo/data_source.rb +27 -0
  32. data/lib/markdo/ics_exporter.rb +65 -0
  33. data/lib/markdo/models/task.rb +46 -0
  34. data/lib/markdo/models/task_attribute.rb +22 -0
  35. data/lib/markdo/models/task_collection.rb +75 -0
  36. data/lib/markdo/version.rb +1 -1
  37. data/markdo.gemspec +6 -0
  38. data/script/build +9 -0
  39. data/spec/fixtures/add_command/Inbox.md +0 -0
  40. data/spec/fixtures/date_commands/Inbox.md +6 -0
  41. data/spec/fixtures/date_commands/Sprint.md +6 -0
  42. data/spec/fixtures/ics_command/Inbox.md +0 -0
  43. data/{test/fixtures/ics_command.md → spec/fixtures/ics_command/Sprint.md} +0 -0
  44. data/spec/fixtures/inbox_command/Inbox.md +2 -0
  45. data/spec/fixtures/inbox_command/Sprint.md +2 -0
  46. data/spec/fixtures/process_command/Backlog.md +0 -0
  47. data/spec/fixtures/process_command/Inbox.md +0 -0
  48. data/spec/fixtures/process_command/Maybe.md +0 -0
  49. data/spec/fixtures/process_command/Sprint.md +0 -0
  50. data/spec/fixtures/query_command/Inbox.md +4 -0
  51. data/spec/fixtures/query_command/Sprint.md +4 -0
  52. data/spec/fixtures/tag_command/Inbox.md +2 -0
  53. data/spec/fixtures/tag_command/Sprint.md +2 -0
  54. data/spec/lib/cli_spec.rb +79 -0
  55. data/spec/lib/commands/add_command_spec.rb +70 -0
  56. data/spec/lib/commands/edit_command_spec.rb +32 -0
  57. data/spec/lib/commands/forecast_command_spec.rb +24 -0
  58. data/spec/lib/commands/ics_command_spec.rb +30 -0
  59. data/spec/lib/commands/inbox_command_spec.rb +22 -0
  60. data/spec/lib/commands/overdue_command_spec.rb +19 -0
  61. data/spec/lib/commands/overview_command_spec.rb +25 -0
  62. data/spec/lib/commands/process_command_spec.rb +178 -0
  63. data/spec/lib/commands/query_command_spec.rb +23 -0
  64. data/spec/lib/commands/star_command_spec.rb +19 -0
  65. data/spec/lib/commands/summary_command_spec.rb +23 -0
  66. data/spec/lib/commands/tag_command_spec.rb +21 -0
  67. data/spec/lib/commands/today_command_spec.rb +19 -0
  68. data/spec/lib/commands/tomorrow_command_spec.rb +19 -0
  69. data/spec/lib/commands/week_command_spec.rb +23 -0
  70. data/spec/lib/ics_exporter_spec.rb +59 -0
  71. data/spec/lib/models/task_attribute_spec.rb +85 -0
  72. data/spec/lib/models/task_collection_spec.rb +168 -0
  73. data/spec/lib/models/task_spec.rb +79 -0
  74. data/spec/spec_helper.rb +40 -0
  75. metadata +189 -42
  76. data/.ruby-version +0 -1
  77. data/lib/markdo/command.rb +0 -12
  78. data/lib/markdo/date_command.rb +0 -9
  79. data/lib/markdo/forecast_command.rb +0 -61
  80. data/lib/markdo/ics_command.rb +0 -73
  81. data/lib/markdo/inbox_command.rb +0 -15
  82. data/lib/markdo/overdue_command.rb +0 -47
  83. data/lib/markdo/overview_command.rb +0 -17
  84. data/lib/markdo/process_command.rb +0 -68
  85. data/lib/markdo/query_command.rb +0 -24
  86. data/lib/markdo/rss_command.rb +0 -67
  87. data/lib/markdo/star_command.rb +0 -9
  88. data/lib/markdo/summary_command.rb +0 -30
  89. data/lib/markdo/tag_command.rb +0 -9
  90. data/lib/markdo/today_command.rb +0 -10
  91. data/lib/markdo/tomorrow_command.rb +0 -10
  92. data/lib/markdo/week_command.rb +0 -36
  93. data/test/fixtures/inbox.md +0 -2
  94. data/test/fixtures/rss_command.md +0 -4
  95. data/test/ics_command_test.rb +0 -38
  96. data/test/inbox_command_test.rb +0 -19
  97. data/test/rss_command_test.rb +0 -39
  98. data/test/summary_command_test.rb +0 -19
  99. 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,11 @@
1
+ require 'markdo/commands/command'
2
+
3
+ module Markdo
4
+ class InboxCommand < Command
5
+ def run
6
+ inbox_task_collection.all.each do |task|
7
+ @stdout.puts(task.line)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'date'
2
+
3
+ module Markdo
4
+ class OverdueCommand < Command
5
+ def run
6
+ task_collection.overdue.each do |task|
7
+ @stdout.puts(task.line)
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ require 'markdo/commands/command'
2
+
3
+ module Markdo
4
+ class StarCommand < Command
5
+ def run
6
+ task_collection.starred.each do |task|
7
+ @stdout.puts(task.line)
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ require 'markdo/commands/command'
2
+
3
+ module Markdo
4
+ class TagCommand < Command
5
+ def run(string)
6
+ task_collection.with_tag(string).each do |task|
7
+ @stdout.puts(task.line)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'date'
2
+ require 'markdo/commands/command'
3
+
4
+ module Markdo
5
+ class TodayCommand < Command
6
+ def run
7
+ task_collection.due_today.each do |task|
8
+ @stdout.puts(task.line)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require 'date'
2
+ require 'markdo/commands/command'
3
+
4
+ module Markdo
5
+ class TomorrowCommand < Command
6
+ def run
7
+ task_collection.due_tomorrow.each do |task|
8
+ @stdout.puts(task.line)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,4 +1,4 @@
1
- require 'markdo/command'
1
+ require 'markdo/commands/command'
2
2
  require 'markdo/version'
3
3
 
4
4
  module Markdo
@@ -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