todotxt 0.1.0 → 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.
@@ -1,55 +1,52 @@
1
1
  module Todotxt
2
2
  module CLIHelpers
3
-
4
- def format_todo(todo, number_padding=nil)
3
+ def format_todo(todo, number_padding = nil)
5
4
  line = todo.line.to_s
6
- if number_padding
7
- line = line.rjust number_padding
8
- end
5
+ line = line.rjust number_padding if number_padding
9
6
 
10
7
  text = todo.to_s
11
8
 
12
- unless todo.done
9
+ if todo.done
10
+ text = text.color(:black).bright
11
+ else
13
12
  text.gsub! PRIORITY_REGEX do |p|
14
- case p[1]
15
- when "A"
16
- color = :red
17
- when "B"
18
- color = :yellow
19
- when "C"
20
- color = :green
21
- else
22
- color = :white
23
- end
13
+ color = case p[1]
14
+ when 'A'
15
+ :red
16
+ when 'B'
17
+ :yellow
18
+ when 'C'
19
+ :green
20
+ else
21
+ :white
22
+ end
24
23
 
25
24
  p.to_s.color(color)
26
25
  end
27
26
 
28
27
  text.gsub! PROJECT_REGEX, '\1'.color(:green)
29
28
  text.gsub! CONTEXT_REGEX, '\1'.color(:blue)
30
- else
31
- text = text.color(:black).bright
32
29
  end
33
30
 
34
- ret = ""
31
+ ret = ''
35
32
 
36
33
  ret << "#{line}. ".color(:black).bright
37
- ret << "#{text}"
34
+ ret << text.to_s
38
35
  end
39
36
 
40
- def warn message=""
37
+ def warn(message = '')
41
38
  puts "WARN: #{message}".color(:yellow)
42
39
  end
43
40
 
44
- def notice message=""
41
+ def notice(message = '')
45
42
  puts "=> #{message}".color(:green)
46
43
  end
47
44
 
48
- def error message=""
45
+ def error(message = '')
49
46
  puts "ERROR: #{message}".color(:red)
50
47
  end
51
48
 
52
- def error_and_exit message =""
49
+ def error_and_exit(message = '')
53
50
  error message
54
51
  exit
55
52
  end
@@ -1,35 +1,63 @@
1
- require "parseconfig"
2
- require "fileutils"
1
+ require 'parseconfig'
2
+ require 'fileutils'
3
3
 
4
4
  module Todotxt
5
+ # Todotxt relies on a configuration file (`.todotxt.cfg`) in your home directory,
6
+ # which points to the location of your todo.txt. You can run:
7
+ #
8
+ # $ todotxt generate_cfg
9
+ #
10
+ # to generate this file, which will then point to `~/todo.txt`.
5
11
  class Config < ParseConfig
6
- def initialize config_file = ""
7
- if config_file.empty?
8
- @config_file = Config.config_path
9
- else
10
- @config_file = config_file
11
- end
12
+ def initialize(options = {})
13
+ @options = options
14
+
15
+ @config_file = options[:config_file] || Config.config_path
12
16
 
13
17
  if file_exists?
14
18
  super @config_file
15
19
  validate
16
20
  else
21
+ # Initialize mandatory values for `ParseConfig`
17
22
  @params = {}
18
23
  @groups = []
24
+ @splitRegex = '\s*=\s*'
25
+ @comments = [';']
19
26
  end
20
27
  end
21
28
 
22
29
  def file_exists?
23
- File.exists? @config_file
30
+ File.exist? @config_file
24
31
  end
25
32
 
26
33
  def files
27
- params["files"] || {"todo" => params["todo_txt_path"] }
34
+ files = {}
35
+ (params['files'] || { 'todo' => params['todo_txt_path'] }).each do |k, p|
36
+ files[k] = TodoFile.new(p)
37
+ end
38
+
39
+ files
40
+ end
41
+
42
+ def file
43
+ if @options[:file].nil?
44
+ files['todo'] || raise("Bad configuration file: 'todo' is a required file.")
45
+ elsif files[@options[:file]]
46
+ files[@options[:file]]
47
+ elsif File.exist?(File.expand_path(@options[:file]))
48
+ TodoFile.new(File.expand_path(@options[:file]))
49
+ else
50
+ raise("\"#{@options[:file]}\" is not defined in the config and not a valid filename.")
51
+ end
52
+ end
53
+
54
+ def editor
55
+ params['editor'] || ENV['EDITOR']
28
56
  end
29
57
 
30
58
  def generate!
31
- FileUtils.copy File.join(File.dirname(File.expand_path(__FILE__)), "..", "..", "conf", "todotxt.cfg"), @config_file
32
- import_config
59
+ FileUtils.copy File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'conf', 'todotxt.cfg'), @config_file
60
+ import_config
33
61
  end
34
62
 
35
63
  def path
@@ -41,17 +69,18 @@ module Todotxt
41
69
  end
42
70
 
43
71
  def self.config_path
44
- File.join ENV["HOME"], ".todotxt.cfg"
72
+ File.join ENV['HOME'], '.todotxt.cfg'
45
73
  end
46
74
 
47
75
  def deprecated?
48
- params["files"].nil?
76
+ params['files'].nil?
49
77
  end
50
78
 
51
79
  private
80
+
52
81
  def validate
53
- if params["files"] && params["todo_txt_path"]
54
- raise "Bad configuration file: use either files or todo_txt_path"
82
+ if params['files'] && params['todo_txt_path']
83
+ raise 'Bad configuration file: use either files or todo_txt_path'
55
84
  end
56
85
  end
57
86
  end
data/lib/todotxt/regex.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Todotxt
2
- PRIORITY_REGEX = /^\(([A-Z])\) /
3
- PROJECT_REGEX = /(\+\w+)/
4
- CONTEXT_REGEX = /(@\w+)/
5
- DATE_REGEX = /^(\([A-Z]\) )?(x )?((\d{4}-)(\d{1,2}-)(\d{1,2}))\s?/
6
- DONE_REGEX = /^(\([A-Z]\) )?x /
2
+ PRIORITY_REGEX = /^\(([A-Z])\) /.freeze
3
+ PROJECT_REGEX = /(\+\w+)/.freeze
4
+ CONTEXT_REGEX = /(@\w+)/.freeze
5
+ DATE_REGEX = /^(\([A-Z]\) )?(x )?((\d{4}-)(\d{1,2}-)(\d{1,2}))\s?/.freeze
6
+ DONE_REGEX = /^(\([A-Z]\) )?x /.freeze
7
7
  end
data/lib/todotxt/todo.rb CHANGED
@@ -1,8 +1,16 @@
1
- require "todotxt/regex"
1
+ require 'todotxt/regex'
2
2
 
3
3
  module Todotxt
4
+ # Represent a task formatted according to
5
+ # [todo.txt format rules](https://github.com/todotxt/todo.txt#todotxt-format-rules)
6
+ #
7
+ # @attr [String] the complete text definition of this task
8
+ # @attr [Integer] line the line number of this task
9
+ # @attr [Char] the character which defines the task's priority
10
+ # @attr [Array] projects list of linked projects
11
+ # @attr [Array] contexts list of linked contexts
12
+ # @attr [Boolean] done `true` if task is done
4
13
  class Todo
5
-
6
14
  attr_accessor :text
7
15
  attr_accessor :line
8
16
  attr_accessor :priority
@@ -10,25 +18,21 @@ module Todotxt
10
18
  attr_accessor :contexts
11
19
  attr_accessor :done
12
20
 
13
- def initialize text, line=nil
21
+ # @param[String] text
22
+ def initialize(text, line = nil)
14
23
  @line = line
15
24
 
16
25
  create_from_text text
17
26
  end
18
27
 
19
- def create_from_text text
20
- @text = text
21
- @priority = text.scan(PRIORITY_REGEX).flatten.first || nil
22
- @projects = text.scan(PROJECT_REGEX).flatten.uniq || []
23
- @contexts = text.scan(CONTEXT_REGEX).flatten.uniq || []
24
- @done = !text.scan(DONE_REGEX).empty?
25
- end
26
-
28
+ # Get due date if set
29
+ # @return [Date|Nil]
27
30
  def due
28
31
  date = Chronic.parse(text.scan(DATE_REGEX).flatten[2])
29
32
  date.nil? ? nil : date.to_date
30
33
  end
31
34
 
35
+ # Mark this task as done
32
36
  def do
33
37
  unless done
34
38
  @text = "x #{text}".strip
@@ -36,23 +40,20 @@ module Todotxt
36
40
  end
37
41
  end
38
42
 
43
+ # Mark this task as not done
39
44
  def undo
40
45
  if done
41
- @text = text.sub(DONE_REGEX, "").strip
46
+ @text = text.sub(DONE_REGEX, '').strip
42
47
  @done = false
43
48
  end
44
49
  end
45
50
 
46
- def prioritize new_priority=nil, opts={}
47
- if new_priority && !new_priority.match(/^[A-Z]$/i)
48
- return
49
- end
51
+ def prioritize(new_priority = nil, opts = {})
52
+ return if new_priority && !new_priority.match(/^[A-Z]$/i)
50
53
 
51
- if new_priority
52
- new_priority = new_priority.upcase
53
- end
54
+ new_priority = new_priority.upcase if new_priority
54
55
 
55
- priority_string = new_priority ? "(#{new_priority}) " : ""
56
+ priority_string = new_priority ? "(#{new_priority}) " : ''
56
57
 
57
58
  if priority && !opts[:force]
58
59
  @text.gsub! PRIORITY_REGEX, priority_string
@@ -63,33 +64,46 @@ module Todotxt
63
64
  @priority = new_priority
64
65
  end
65
66
 
66
- def append appended_text=""
67
- @text << " " << appended_text
67
+ # Add some text to the end of this task definition
68
+ def append(appended_text = '')
69
+ @text << ' ' << appended_text
68
70
  end
69
71
 
70
- def prepend prepended_text=""
72
+ # Add some text to the beginning of this task definition
73
+ def prepend(prepended_text = '')
71
74
  @text = "#{prepended_text} #{text.gsub(PRIORITY_REGEX, '')}"
72
- prioritize priority, :force => true
75
+ prioritize priority, force: true
73
76
  end
74
77
 
75
- def replace text
78
+ def replace(text)
76
79
  create_from_text text
77
80
  end
78
81
 
82
+ # @return [String]
79
83
  def to_s
80
84
  text.clone
81
85
  end
82
86
 
83
- def <=> b
84
- if priority.nil? && b.priority.nil?
85
- return line <=> b.line
86
- end
87
+ # Compare with another `Todo` based on `line` and `priority` attributes
88
+ # @param [Todo] b
89
+ def <=>(b)
90
+ return 1 unless b.is_a? Todo
91
+ return line <=> b.line if priority.nil? && b.priority.nil?
87
92
 
88
93
  return 1 if priority.nil?
89
94
  return -1 if b.priority.nil?
90
95
 
91
- return priority <=> b.priority
96
+ priority <=> b.priority
92
97
  end
93
98
 
99
+ private
100
+
101
+ def create_from_text(text)
102
+ @text = text
103
+ @priority = text.scan(PRIORITY_REGEX).flatten.first || nil
104
+ @projects = text.scan(PROJECT_REGEX).flatten.uniq || []
105
+ @contexts = text.scan(CONTEXT_REGEX).flatten.uniq || []
106
+ @done = !text.scan(DONE_REGEX).empty?
107
+ end
94
108
  end
95
109
  end
@@ -1,40 +1,40 @@
1
1
  module Todotxt
2
2
  class TodoFile
3
-
4
- def initialize path
3
+ def initialize(path)
5
4
  @path = File.expand_path(path)
6
5
  end
7
6
 
8
7
  # Generate a file from template
9
8
  def generate!
10
- FileUtils.copy File.join(File.dirname(File.expand_path(__FILE__)), "..", "..", "conf", "todo.txt"), @path
9
+ FileUtils.copy File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'conf', 'todo.txt'), @path
11
10
  end
12
11
 
13
- def path
14
- @path
15
- end
12
+ attr_reader :path
16
13
 
17
14
  def basename
18
15
  File.basename @path
19
16
  end
20
17
 
21
18
  def exists?
22
- File.exists? File.expand_path(@path)
19
+ File.exist? File.expand_path(@path)
23
20
  end
24
21
 
25
22
  def self.from_key(key)
26
23
  config = Todotxt::Config.new
27
- if config.files.has_key? key
24
+ if config.files.key? key
28
25
  path = config.files[key]
29
- self.new path
26
+ new path
30
27
  else
31
- raise "Key not found in config"
28
+ raise 'Key not found in config'
32
29
  end
33
30
  end
34
31
 
35
-
36
32
  def to_s
37
33
  @path
38
34
  end
35
+
36
+ def ==(other)
37
+ @path == other.path
38
+ end
39
39
  end
40
40
  end
@@ -1,13 +1,19 @@
1
- require "todotxt/todo"
1
+ require 'todotxt/todo'
2
2
 
3
3
  module Todotxt
4
- #@TODO merge with TodoFile, both overlap too much
4
+ # Represent a collection of `Todo` items
5
+ # TODO merge with TodoFile, both overlap too much
5
6
  class TodoList
6
7
  include Enumerable
7
8
 
8
- attr_accessor :todos
9
+ attr_reader :todos
9
10
 
10
- def initialize file
11
+ # @INK: refactor TodoList and TodoFile
12
+ # So that TodoFile contains all IO ad List is no longer dependent on file.
13
+ # That way, todolist lsa|listall can use multiple TodoFiles to generate one TodoList
14
+
15
+ def initialize(file, line = nil)
16
+ @line = line || 0
11
17
  @todos = []
12
18
  @file = file
13
19
 
@@ -16,44 +22,51 @@ module Todotxt
16
22
  end
17
23
  end
18
24
 
19
- def add str
20
- todo = Todo.new str, (@todos.count + 1)
25
+ # @param [String] str add the given todo string definition to the list
26
+ # TODO also support `Todo` object
27
+ def add(str)
28
+ todo = Todo.new str, (@line += 1)
21
29
  @todos.push todo
22
30
  @todos.sort!
23
31
 
24
- return todo
32
+ todo
25
33
  end
26
34
 
27
- def remove line
35
+ # @param [Todo|String] line remove the given todo to the list
36
+ def remove(line)
28
37
  @todos.reject! { |t| t.line.to_s == line.to_s }
29
38
  end
30
39
 
31
- def move line, other_list
40
+ def move(line, other_list)
32
41
  other_list.add find_by_line(line).to_s
33
42
  remove line
34
43
  end
35
44
 
45
+ # Get all projects from todo definitions
46
+ # @return[Array<String>]
36
47
  def projects
37
- map { |t| t.projects }.flatten.uniq.sort
48
+ map(&:projects).flatten.uniq.sort
38
49
  end
39
50
 
51
+ # Get all contexts from todo definitions
52
+ # @return[Array<String>]
40
53
  def contexts
41
- map { |t| t.contexts }.flatten.uniq.sort
54
+ map(&:contexts).flatten.uniq.sort
42
55
  end
43
56
 
44
- def find_by_line line
57
+ def find_by_line(line)
45
58
  @todos.find { |t| t.line.to_s == line.to_s }
46
59
  end
47
60
 
48
61
  def save
49
- File.open(@file.path, "w") { |f| f.write to_txt }
62
+ File.open(@file.path, 'w') { |f| f.write to_txt }
50
63
  end
51
64
 
52
- def each &block
65
+ def each(&block)
53
66
  @todos.each &block
54
67
  end
55
68
 
56
- def filter search="", opts={}
69
+ def filter(search = '', opts = {})
57
70
  @todos.select! do |t|
58
71
  select = false
59
72
 
@@ -77,16 +90,16 @@ module Todotxt
77
90
  self
78
91
  end
79
92
 
80
- def on_date date
93
+ def on_date(date)
81
94
  @todos.select { |t| t.due == date }
82
95
  end
83
96
 
84
- def before_date date
97
+ def before_date(date)
85
98
  @todos.reject { |t| t.due.nil? || t.due >= date }
86
99
  end
87
100
 
88
101
  def to_txt
89
- @todos.sort { |a,b| a.line <=> b.line }.map { |t| t.to_s.strip }.join("\n")
102
+ @todos.sort_by(&:line).map { |t| t.to_s.strip }.join("\n")
90
103
  end
91
104
 
92
105
  def to_s
@@ -96,6 +109,5 @@ module Todotxt
96
109
  def to_a
97
110
  map { |t| ["#{t.line}. ", t.to_s] }
98
111
  end
99
-
100
112
  end
101
113
  end