diff --git a/bin/todotxt b/bin/todotxt index d06b5c4..72b5aad 100755 --- a/bin/todotxt +++ b/bin/todotxt @@ -1,13 +1,13 @@ #!/usr/bin/env ruby -libdir = File.join(File.dirname(File.dirname(__FILE__)), "lib") +libdir = File.join(File.dirname(File.dirname(__FILE__)), 'lib') $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) args = ARGV.clone -#if args.empty? - #args.push "ls" -#end +# if args.empty? +# args.push "ls" +# end -require "todotxt" +require 'todotxt' Todotxt::CLI.start(args) diff --git a/lib/todotxt.rb b/lib/todotxt.rb index 2c0f3a4..a7d2d44 100644 --- a/lib/todotxt.rb +++ b/lib/todotxt.rb @@ -3,13 +3,13 @@ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) module Todotxt - autoload :Todo, "todotxt/todo" - autoload :TodoList, "todotxt/todolist" - autoload :TodoFile, "todotxt/todofile" - autoload :CLI, "todotxt/cli" - autoload :CLIHelpers, "todotxt/clihelpers" - autoload :Config, "todotxt/config" + autoload :Todo, 'todotxt/todo' + autoload :TodoList, 'todotxt/todolist' + autoload :TodoFile, 'todotxt/todofile' + autoload :CLI, 'todotxt/cli' + autoload :CLIHelpers, 'todotxt/clihelpers' + autoload :Config, 'todotxt/config' end -require "todotxt/regex" -require "todotxt/version" +require 'todotxt/regex' +require 'todotxt/version' diff --git a/lib/todotxt/cli.rb b/lib/todotxt/cli.rb index f5a1569..44014bb 100644 --- a/lib/todotxt/cli.rb +++ b/lib/todotxt/cli.rb @@ -1,42 +1,41 @@ -require "thor" -require "rainbow" -require "chronic" -require "parseconfig" +require 'thor' +require 'rainbow' +require 'chronic' +require 'parseconfig' module Todotxt - CFG_PATH = File.expand_path("~/.todotxt.cfg") + CFG_PATH = File.expand_path('~/.todotxt.cfg') class CLI < Thor include Thor::Actions include Todotxt::CLIHelpers def self.source_root - File.join File.dirname(__FILE__), "..", "..", "conf" + File.join File.dirname(__FILE__), '..', '..', 'conf' end def initialize(*args) super # Allow testing colors, rainbow usually detects whether - # the output goes to a TTY, but Aruba/Cucumber is not a + # the output goes to a TTY, but Aruba/Cucumber is not a # TTY, so we enforce it here, based on an environment var - Sickill::Rainbow.enabled = true if ENV["FORCE_COLORS"] == "TRUE" + Sickill::Rainbow.enabled = true if ENV['FORCE_COLORS'] == 'TRUE' # Open config file and render config. @config = Config.new options @list = nil - unless ["help", "generate_config", "generate_txt"].include? ARGV[0] + unless %w[help generate_config generate_txt].include? ARGV[0] ask_and_create @config unless @config.file_exists? - if @config.deprecated? and options[:file] - error_and_exit "You are using an old config, which has no support for mulitple files. Please update your configuration." + if @config.deprecated? && options[:file] + error_and_exit 'You are using an old config, which has no support for mulitple files. Please update your configuration.' end ask_and_create @config.file unless @config.file.exists? @list = TodoList.new @config.file end - end - class_option :file, :type => :string, :desc => "Use a different file than todo.txt + class_option :file, type: :string, desc: "Use a different file than todo.txt E.g. use 'done' to have the action performed on the file you set for 'done' in the todotxt configuration under [files]." @@ -46,57 +45,57 @@ def initialize(*args) # Listing # - desc "list | ls [SEARCH]", "List all todos, or todos matching SEARCH" - method_option :done, :type => :boolean, :aliases => "-d", :desc => "Include todo items that have been marked as done" - method_option :simple, :type => :boolean, :desc => "Simple output (for scripts, etc)" - method_option :all, :type => :boolean, :aliases => "-a", :desc => "List items from all files" - def list search="" + desc 'list | ls [SEARCH]', 'List all todos, or todos matching SEARCH' + method_option :done, type: :boolean, aliases: '-d', desc: 'Include todo items that have been marked as done' + method_option :simple, type: :boolean, desc: 'Simple output (for scripts, etc)' + method_option :all, type: :boolean, aliases: '-a', desc: 'List items from all files' + def list(search = '') if options[:all] @config.files.each do |file| count = @list.todos.count || 0 - @list.todos += TodoList.new(file[1], count).todos unless file[0] == "todo" + @list.todos += TodoList.new(file[1], count).todos unless file[0] == 'todo' end end - @list.filter(search, :with_done => (options[:done] ? true : false)) - render_list :simple => !!options[:simple] + @list.filter(search, with_done: (options[:done] ? true : false)) + render_list simple: !!options[:simple] end - map "ls" => :list + map 'ls' => :list - desc "lsdone | lsd", "List all done items" - def lsdone search="" - @list.filter(search, :only_done => true) + desc 'lsdone | lsd', 'List all done items' + def lsdone(search = '') + @list.filter(search, only_done: true) render_list end - map "lsd" => :lsdone + map 'lsd' => :lsdone - desc "listproj | lsproj", "List all projects" + desc 'listproj | lsproj', 'List all projects' def listproj @list.projects.each { |p| say p } end - map "lsproj" => :listproj + map 'lsproj' => :listproj - desc "lscon | lsc", "List all contexts" + desc 'lscon | lsc', 'List all contexts' def lscon @list.contexts.each { |c| say c } end - map "lsc" => :lscon + map 'lsc' => :lscon - desc "due", "List due items" + desc 'due', 'List due items' def due - if ENV["date"] # Allow testing to "freeze" the date - today = DateTime.parse(ENV["date"]).to_date - else - today = DateTime.now.to_date - end + today = if ENV['date'] # Allow testing to "freeze" the date + DateTime.parse(ENV['date']).to_date + else + DateTime.now.to_date + end - puts "Due today (#{today.strftime("%Y-%m-%d")})".bright + puts "Due today (#{today.strftime('%Y-%m-%d')})".bright @list.on_date(today).each { |todo| puts format_todo(todo) } puts "\nPast-due items".bright @list.before_date(today).each { |todo| puts format_todo(todo) } puts "\nDue 7 days in advance".bright - ((today+1)..(today+7)).each do |day| + ((today + 1)..(today + 7)).each do |day| @list.on_date(day).each { |todo| puts format_todo(todo) } end end @@ -105,7 +104,7 @@ def due # Todo management # - desc "add | a TEXT", "Add a new Todo item" + desc 'add | a TEXT', 'Add a new Todo item' def add(str, *str2) string = "#{str} #{str2.join(' ')}" todo = @list.add string @@ -114,10 +113,10 @@ def add(str, *str2) @list.save end - map "a" => :add + map 'a' => :add - desc "do ITEM#[, ITEM#, ITEM#, ...]", "Mark ITEM# as done" - def do line1, *lines + desc 'do ITEM#[, ITEM#, ITEM#, ...]', 'Mark ITEM# as done' + def do(line1, *lines) lines.unshift(line1).each do |line| todo = @list.find_by_line line if todo @@ -131,8 +130,8 @@ def do line1, *lines end end - desc "undo | u ITEM#[, ITEM#, ITEM#, ...]", "Mark ITEM# item as not done" - def undo line1, *lines + desc 'undo | u ITEM#[, ITEM#, ITEM#, ...]', 'Mark ITEM# item as not done' + def undo(line1, *lines) lines.unshift(line1).each do |line| todo = @list.find_by_line line if todo @@ -145,10 +144,10 @@ def undo line1, *lines end end end - map "u" => :undo + map 'u' => :undo - desc "pri | p ITEM# PRIORITY", "Set priority of ITEM# to PRIORITY" - def pri line, priority + desc 'pri | p ITEM# PRIORITY', 'Set priority of ITEM# to PRIORITY' + def pri(line, priority) todo = @list.find_by_line line if todo todo.prioritize priority @@ -159,10 +158,10 @@ def pri line, priority error "No todo found at line #{line}" end end - map "p" => :pri + map 'p' => :pri - desc "dp | depri ITEM#[, ITEM#, ITEM#, ...]", "Remove priority for ITEM#" - def dp line1, *lines + desc 'dp | depri ITEM#[, ITEM#, ITEM#, ...]', 'Remove priority for ITEM#' + def dp(line1, *lines) lines.unshift(line1).each do |line| todo = @list.find_by_line line if todo @@ -175,10 +174,10 @@ def dp line1, *lines end end end - map "depri" => :dp + map 'depri' => :dp - desc "append | app ITEM# STRING", "Append STRING to ITEM#" - def append line, str, *str2 + desc 'append | app ITEM# STRING', 'Append STRING to ITEM#' + def append(line, str, *str2) string = "#{str} #{str2.join(' ')}" todo = @list.find_by_line line if todo @@ -190,10 +189,10 @@ def append line, str, *str2 error "No todo found at line #{line}" end end - map "app" => :append + map 'app' => :append - desc "prepend | prep ITEM# STRING", "Prepend STRING to ITEM#" - def prepend line, str, *str2 + desc 'prepend | prep ITEM# STRING', 'Prepend STRING to ITEM#' + def prepend(line, str, *str2) string = "#{str} #{str2.join(' ')}" todo = @list.find_by_line line if todo @@ -205,10 +204,10 @@ def prepend line, str, *str2 error "No todo found at line #{line}" end end - map "prep" => :prepend + map 'prep' => :prepend - desc "replace ITEM# TEXT", "Completely replace ITEM# text with TEXT" - def replace line, str, *str2 + desc 'replace ITEM# TEXT', 'Completely replace ITEM# text with TEXT' + def replace(line, str, *str2) string = "#{str} #{str2.join(' ')}" todo = @list.find_by_line line if todo @@ -221,16 +220,16 @@ def replace line, str, *str2 end end - desc "del | rm ITEM#[, ITEM#, ITEM#, ...]", "Remove ITEM#" - method_option :force, :type => :boolean, :aliases => "-f", :desc => "Don't confirm removal" - def del line1, *lines + desc 'del | rm ITEM#[, ITEM#, ITEM#, ...]', 'Remove ITEM#' + method_option :force, type: :boolean, aliases: '-f', desc: "Don't confirm removal" + def del(line1, *lines) lines.unshift(line1).each do |line| todo = @list.find_by_line line if todo say format_todo(todo) - if options[:force] || yes?("Remove this item? [y/N]") + if options[:force] || yes?('Remove this item? [y/N]') @list.remove line - notice "Removed from list" + notice 'Removed from list' @list.save end @@ -239,15 +238,15 @@ def del line1, *lines end end end - map "rm" => :del + map 'rm' => :del - desc "edit", "Open todo.txt file in your default editor" + desc 'edit', 'Open todo.txt file in your default editor' def edit Kernel.system "#{@config.editor} #{@config.file.path}" end - desc "move | mv ITEM#[, ITEM#, ITEM#, ...] file", "Move ITEM# to another file" - def move line1, *lines, other_list_alias + desc 'move | mv ITEM#[, ITEM#, ITEM#, ...] file', 'Move ITEM# to another file' + def move(line1, *lines, other_list_alias) if @config.files[other_list_alias].nil? error_and_exit "File alias #{other_list_alias} not found" else @@ -268,41 +267,42 @@ def move line1, *lines, other_list_alias end end end - map "mv" => :move + map 'mv' => :move # # File generation # - desc "generate_config", "Create a .todotxt.cfg file in your home folder, containing the path to todo.txt" + desc 'generate_config', 'Create a .todotxt.cfg file in your home folder, containing the path to todo.txt' def generate_config - copy_file "todotxt.cfg", Config.config_path - puts "" + copy_file 'todotxt.cfg', Config.config_path + puts '' end - desc "generate_txt", "Create a sample todo.txt" + desc 'generate_txt', 'Create a sample todo.txt' def generate_txt - copy_file "todo.txt", @file - puts "" + copy_file 'todo.txt', @file + puts '' end # # Extras # - desc "version", "Show todotxt version" + desc 'version', 'Show todotxt version' def version say "todotxt #{VERSION}" end - private - def render_list opts={} + private + + def render_list(opts = {}) numsize = @list.count + 1 numsize = numsize.to_s.length + 0 @list.each do |t| if opts[:simple] - say "#{t.line} #{t.to_s}" + say "#{t.line} #{t}" else say format_todo(t, numsize) end @@ -314,14 +314,14 @@ def render_list opts={} end # File should respond_to "basename", "path" and "generate!" - def ask_and_create file + def ask_and_create(file) puts "#{file.basename} doesn't exist yet. Would you like to generate a sample file?" confirm_generate = yes? "Create #{file.path}? [y/N]" if confirm_generate file.generate! else - puts "" + puts '' exit end end diff --git a/lib/todotxt/clihelpers.rb b/lib/todotxt/clihelpers.rb index 75901e7..20ee58a 100644 --- a/lib/todotxt/clihelpers.rb +++ b/lib/todotxt/clihelpers.rb @@ -1,55 +1,52 @@ module Todotxt module CLIHelpers - - def format_todo(todo, number_padding=nil) + def format_todo(todo, number_padding = nil) line = todo.line.to_s - if number_padding - line = line.rjust number_padding - end + line = line.rjust number_padding if number_padding text = todo.to_s - unless todo.done + if todo.done + text = text.color(:black).bright + else text.gsub! PRIORITY_REGEX do |p| - case p[1] - when "A" - color = :red - when "B" - color = :yellow - when "C" - color = :green - else - color = :white - end + color = case p[1] + when 'A' + :red + when 'B' + :yellow + when 'C' + :green + else + :white + end p.to_s.color(color) end text.gsub! PROJECT_REGEX, '\1'.color(:green) text.gsub! CONTEXT_REGEX, '\1'.color(:blue) - else - text = text.color(:black).bright end - ret = "" + ret = '' ret << "#{line}. ".color(:black).bright - ret << "#{text}" + ret << text.to_s end - def warn message="" + def warn(message = '') puts "WARN: #{message}".color(:yellow) end - def notice message="" + def notice(message = '') puts "=> #{message}".color(:green) end - def error message="" + def error(message = '') puts "ERROR: #{message}".color(:red) end - def error_and_exit message ="" + def error_and_exit(message = '') error message exit end diff --git a/lib/todotxt/config.rb b/lib/todotxt/config.rb index 927a7ae..a75fbb9 100644 --- a/lib/todotxt/config.rb +++ b/lib/todotxt/config.rb @@ -1,9 +1,9 @@ -require "parseconfig" -require "fileutils" +require 'parseconfig' +require 'fileutils' module Todotxt class Config < ParseConfig - def initialize options = {} + def initialize(options = {}) @options = options @config_file = options[:config_file] || Config.config_path @@ -18,12 +18,12 @@ def initialize options = {} end def file_exists? - File.exists? @config_file + File.exist? @config_file end def files files = {} - (params["files"] || {"todo" => params["todo_txt_path"] }).each do |k,p| + (params['files'] || { 'todo' => params['todo_txt_path'] }).each do |k, p| files[k] = TodoFile.new(p) end @@ -32,10 +32,10 @@ def files def file if @options[:file].nil? - files["todo"] || raise("Bad configuration file: 'todo' is a required file.") + files['todo'] || raise("Bad configuration file: 'todo' is a required file.") elsif files[@options[:file]] files[@options[:file]] - elsif File.exists?(File.expand_path(@options[:file])) + elsif File.exist?(File.expand_path(@options[:file])) TodoFile.new(File.expand_path(@options[:file])) else raise("\"#{@options[:file]}\" is not defined in the config and not a valid filename.") @@ -43,12 +43,12 @@ def file end def editor - params["editor"] || ENV["EDITOR"] + params['editor'] || ENV['EDITOR'] end def generate! - FileUtils.copy File.join(File.dirname(File.expand_path(__FILE__)), "..", "..", "conf", "todotxt.cfg"), @config_file - import_config + FileUtils.copy File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'conf', 'todotxt.cfg'), @config_file + import_config end def path @@ -60,17 +60,18 @@ def basename end def self.config_path - File.join ENV["HOME"], ".todotxt.cfg" + File.join ENV['HOME'], '.todotxt.cfg' end def deprecated? - params["files"].nil? + params['files'].nil? end private + def validate - if params["files"] && params["todo_txt_path"] - raise "Bad configuration file: use either files or todo_txt_path" + if params['files'] && params['todo_txt_path'] + raise 'Bad configuration file: use either files or todo_txt_path' end end end diff --git a/lib/todotxt/regex.rb b/lib/todotxt/regex.rb index b8c3259..1665b71 100644 --- a/lib/todotxt/regex.rb +++ b/lib/todotxt/regex.rb @@ -1,7 +1,7 @@ module Todotxt - PRIORITY_REGEX = /^\(([A-Z])\) / - PROJECT_REGEX = /(\+\w+)/ - CONTEXT_REGEX = /(@\w+)/ - DATE_REGEX = /^(\([A-Z]\) )?(x )?((\d{4}-)(\d{1,2}-)(\d{1,2}))\s?/ - DONE_REGEX = /^(\([A-Z]\) )?x / + PRIORITY_REGEX = /^\(([A-Z])\) /.freeze + PROJECT_REGEX = /(\+\w+)/.freeze + CONTEXT_REGEX = /(@\w+)/.freeze + DATE_REGEX = /^(\([A-Z]\) )?(x )?((\d{4}-)(\d{1,2}-)(\d{1,2}))\s?/.freeze + DONE_REGEX = /^(\([A-Z]\) )?x /.freeze end diff --git a/lib/todotxt/todo.rb b/lib/todotxt/todo.rb index 4706244..b6af61f 100644 --- a/lib/todotxt/todo.rb +++ b/lib/todotxt/todo.rb @@ -1,8 +1,7 @@ -require "todotxt/regex" +require 'todotxt/regex' module Todotxt class Todo - attr_accessor :text attr_accessor :line attr_accessor :priority @@ -10,13 +9,13 @@ class Todo attr_accessor :contexts attr_accessor :done - def initialize text, line=nil + def initialize(text, line = nil) @line = line create_from_text text end - def create_from_text text + def create_from_text(text) @text = text @priority = text.scan(PRIORITY_REGEX).flatten.first || nil @projects = text.scan(PROJECT_REGEX).flatten.uniq || [] @@ -38,21 +37,17 @@ def do def undo if done - @text = text.sub(DONE_REGEX, "").strip + @text = text.sub(DONE_REGEX, '').strip @done = false end end - def prioritize new_priority=nil, opts={} - if new_priority && !new_priority.match(/^[A-Z]$/i) - return - end + def prioritize(new_priority = nil, opts = {}) + return if new_priority && !new_priority.match(/^[A-Z]$/i) - if new_priority - new_priority = new_priority.upcase - end + new_priority = new_priority.upcase if new_priority - priority_string = new_priority ? "(#{new_priority}) " : "" + priority_string = new_priority ? "(#{new_priority}) " : '' if priority && !opts[:force] @text.gsub! PRIORITY_REGEX, priority_string @@ -63,16 +58,16 @@ def prioritize new_priority=nil, opts={} @priority = new_priority end - def append appended_text="" - @text << " " << appended_text + def append(appended_text = '') + @text << ' ' << appended_text end - def prepend prepended_text="" + def prepend(prepended_text = '') @text = "#{prepended_text} #{text.gsub(PRIORITY_REGEX, '')}" - prioritize priority, :force => true + prioritize priority, force: true end - def replace text + def replace(text) create_from_text text end @@ -80,16 +75,13 @@ def to_s text.clone end - def <=> b - if priority.nil? && b.priority.nil? - return line <=> b.line - end + def <=>(b) + return line <=> b.line if priority.nil? && b.priority.nil? return 1 if priority.nil? return -1 if b.priority.nil? - return priority <=> b.priority + priority <=> b.priority end - end end diff --git a/lib/todotxt/todofile.rb b/lib/todotxt/todofile.rb index 6ba8b3f..915e19c 100644 --- a/lib/todotxt/todofile.rb +++ b/lib/todotxt/todofile.rb @@ -1,34 +1,31 @@ module Todotxt class TodoFile - - def initialize path + def initialize(path) @path = File.expand_path(path) end # Generate a file from template def generate! - FileUtils.copy File.join(File.dirname(File.expand_path(__FILE__)), "..", "..", "conf", "todo.txt"), @path + FileUtils.copy File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'conf', 'todo.txt'), @path end - def path - @path - end + attr_reader :path def basename File.basename @path end def exists? - File.exists? File.expand_path(@path) + File.exist? File.expand_path(@path) end def self.from_key(key) config = Todotxt::Config.new - if config.files.has_key? key + if config.files.key? key path = config.files[key] - self.new path + new path else - raise "Key not found in config" + raise 'Key not found in config' end end diff --git a/lib/todotxt/todolist.rb b/lib/todotxt/todolist.rb index b24433e..9e13a80 100644 --- a/lib/todotxt/todolist.rb +++ b/lib/todotxt/todolist.rb @@ -1,7 +1,7 @@ -require "todotxt/todo" +require 'todotxt/todo' module Todotxt - #@TODO merge with TodoFile, both overlap too much + # @TODO merge with TodoFile, both overlap too much class TodoList include Enumerable @@ -11,7 +11,7 @@ class TodoList # So that TodoFile contains all IO ad List is no longer dependent on file. # That way, todolist lsa|listall can use multiple TodoFiles to generate one TodoList - def initialize file, line = nil + def initialize(file, line = nil) @line = line || 0 @todos = [] @file = file @@ -21,44 +21,44 @@ def initialize file, line = nil end end - def add str + def add(str) todo = Todo.new str, (@line += 1) @todos.push todo @todos.sort! - return todo + todo end - def remove line + def remove(line) @todos.reject! { |t| t.line.to_s == line.to_s } end - def move line, other_list + def move(line, other_list) other_list.add find_by_line(line).to_s remove line end def projects - map { |t| t.projects }.flatten.uniq.sort + map(&:projects).flatten.uniq.sort end def contexts - map { |t| t.contexts }.flatten.uniq.sort + map(&:contexts).flatten.uniq.sort end - def find_by_line line + def find_by_line(line) @todos.find { |t| t.line.to_s == line.to_s } end def save - File.open(@file.path, "w") { |f| f.write to_txt } + File.open(@file.path, 'w') { |f| f.write to_txt } end - def each &block + def each(&block) @todos.each &block end - def filter search="", opts={} + def filter(search = '', opts = {}) @todos.select! do |t| select = false @@ -82,16 +82,16 @@ def filter search="", opts={} self end - def on_date date + def on_date(date) @todos.select { |t| t.due == date } end - def before_date date + def before_date(date) @todos.reject { |t| t.due.nil? || t.due >= date } end def to_txt - @todos.sort { |a,b| a.line <=> b.line }.map { |t| t.to_s.strip }.join("\n") + @todos.sort_by(&:line).map { |t| t.to_s.strip }.join("\n") end def to_s @@ -101,6 +101,5 @@ def to_s def to_a map { |t| ["#{t.line}. ", t.to_s] } end - end end diff --git a/lib/todotxt/version.rb b/lib/todotxt/version.rb index ce2b138..1d6b25a 100644 --- a/lib/todotxt/version.rb +++ b/lib/todotxt/version.rb @@ -1,3 +1,3 @@ module Todotxt - VERSION = "0.1.0" + VERSION = '0.1.0'.freeze end