markdo 0.1.12.alpha → 0.2.0

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.
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