todotxt 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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