#!/usr/bin/env ruby $LOAD_PATH.unshift File.join(__dir__, '..', 'lib') require 'gli' require 'doing' require 'tempfile' def class_exists?(class_name) klass = Module.const_get(class_name) return klass.is_a?(Class) rescue NameError return false end if class_exists? 'Encoding' Encoding.default_external = Encoding::UTF_8 if Encoding.respond_to?('default_external') Encoding.default_internal = Encoding::UTF_8 if Encoding.respond_to?('default_internal') end include GLI::App version Doing::VERSION wwid = WWID.new wwid.user_home = %x{echo $HOME}.strip wwid.configure program_desc 'A CLI for a What Was I Doing system' default_command :recent # sort_help :manually desc 'Output notes if included in the template' default_value true switch [:notes], :default_value => true, :negatable => true # desc 'Wrap notes at X chars (0 for no wrap)' # flag [:w,:wrapwidth], :must_match => /^\d+$/, :type => Integer desc 'Specify a different doing_file' flag [:f, :doing_file] desc 'Add an entry' arg_name 'entry' command :now do |c| c.desc 'Section' c.arg_name 'section_name' c.default_value wwid.current_section c.flag [:s,:section], :default_value => wwid.current_section c.desc "Edit entry with #{ENV['EDITOR']}" c.switch [:e,:editor] c.desc 'Backdate start time [4pm|20m|2h|yesterday noon]' c.flag [:back] c.desc 'Timed entry, marks last entry in section as @done' c.default_value false c.switch [:f,:finish_last], :negatable => false, :default_value => false c.desc 'Note' c.arg_name 'note_text' c.flag [:n,:note] # c.desc "Edit entry with specified app" # c.arg_name 'editor_app' # c.default_value wwid.config.has_key?('editor_app') && wwid.config['editor_app'] ? wwid.config['editor_app'] : false # c.flag [:a,:app] c.action do |global_options,options,args| if options[:back] date = wwid.chronify(options[:back]) raise "Unable to parse date string" if date.nil? else date = Time.now end section = wwid.guess_section(options[:s]) || options[:s].cap_first if options[:e] || (args.length == 0 && STDIN.stat.size == 0) raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil? input = "" input += args.join(" ") if args.length > 0 input = wwid.fork_editor(input) if input title, note = wwid.format_input(input) note.push(options[:n]) if options[:n] wwid.add_item(title.cap_first, section, {:note => note, :back => date, :timed => options[:f]}) wwid.write(wwid.doing_file) else raise "No content" end else if args.length > 0 title, note = wwid.format_input(args.join(" ")) note.push(options[:n]) if options[:n] wwid.add_item(title.cap_first, section, {:note => note, :back => date, :timed => options[:f]}) wwid.write(wwid.doing_file) elsif STDIN.stat.size > 0 title, note = wwid.format_input(STDIN.read) note.push(options[:n]) if options[:n] wwid.add_item(title.cap_first, section, {:note => note, :back => date, :timed => options[:f]}) wwid.write(wwid.doing_file) else raise "You must provide content when creating a new entry" end end end end desc 'Add a note to the last entry' arg_name 'note_text' command :note do |c| c.desc 'Section' c.arg_name 'section_name' c.default_value wwid.current_section c.flag [:s,:section], :default_value => wwid.current_section c.desc "Edit entry with #{ENV['EDITOR']}" c.switch [:e,:editor], :negatable => false, :default_value => false c.desc "Replace/Remove last entry's note" c.long_desc "If -r is provided with no other arguments, the last note is removed. If new content is specified through arguments or STDIN, any previous note will be replaced with the new one." c.switch [:r,:remove], :negatable => false, :default_value => false c.action do |global_options,options,args| section = wwid.guess_section(options[:s]) || options[:s].cap_first if options[:e] || (args.length == 0 && STDIN.stat.size == 0 && !options[:r]) raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil? input = args.length > 0 ? args.join(" ") : "" prev_input = wwid.last_note(section) || "" if prev_input.class == Array prev_input = prev_input.join("\n") end input = prev_input + input input = wwid.fork_editor(input) if input title, note = wwid.format_input(input) note.insert(0, title) wwid.note_last(section, note, true) else raise "No content, cancelled" end else if args.length > 0 title, note = wwid.format_input(args.join(" ")) note.insert(0, title) wwid.note_last(section, note, options[:r]) elsif STDIN.stat.size > 0 title, note = wwid.format_input(STDIN.read) note.insert(0, title) wwid.note_last(section, note, options[:r]) else if options[:r] wwid.note_last(section, [], true) else raise "You must provide content when adding a note" end end end wwid.write(wwid.doing_file) end end desc 'Finish any running @meanwhile tasks and optionally create a new one' arg_name 'entry' command :meanwhile do |c| c.desc 'Section' c.arg_name 'section_name' c.default_value wwid.current_section c.flag [:s,:section], :default_value => wwid.current_section c.desc "Edit entry with #{ENV['EDITOR']}" c.switch [:e,:editor] c.desc "Archive previous @meanwhile entry" c.switch [:a,:archive], :default_value => false c.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]' c.flag [:back] c.desc 'Note' c.arg_name 'note_text' c.flag [:n,:note] c.action do |global_options,options,args| if options[:back] date = wwid.chronify(options[:back]) raise "Unable to parse date string" if date.nil? else date = Time.now end section = wwid.guess_section(options[:s]) || options[:s].cap_first input = "" if options[:e] raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil? input += args.join(" ") if args.length > 0 input = wwid.fork_editor(input) else if args.length > 0 input = args.join(" ") elsif STDIN.stat.size > 0 input = STDIN.read end end input = false unless input && input.length > 0 note = options[:n] ? options[:n] : false wwid.stop_start("meanwhile",{:new_item => input, :back => date, :section => section, :archive => options[:a], :note => note}) wwid.write(wwid.doing_file) end end desc 'Add an item to the Later section' arg_name 'entry' command :later do |c| c.desc "Edit entry with #{ENV['EDITOR']}" c.switch [:e,:editor] c.desc "Edit entry with specified app" c.arg_name 'editor_app' c.default_value wwid.config.has_key?('editor_app') && wwid.config['editor_app'] ? wwid.config['editor_app'] : false c.flag [:a,:app] c.desc 'Backdate start time to date string [4pm|20m|2h|yesterday noon]' c.flag [:back] c.desc 'Note' c.arg_name 'note_text' c.flag [:n,:note] c.action do |global_options,options,args| if options[:back] date = wwid.chronify(options[:back]) raise "Unable to parse date string" if date.nil? else date = Time.now end if options[:e] || (args.length == 0 && STDIN.stat.size == 0) raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil? input = "" input += args.join(" ") if args.length > 0 input = wwid.fork_editor(input) if input title, note = wwid.format_input(input) note.push(options[:n]) if options[:n] wwid.add_item(title.cap_first, "Later", {:note => note, :back => date}) wwid.write(wwid.doing_file) else raise "No content" end else if args.length > 0 title, note = wwid.format_input(args.join(" ")) note.push(options[:n]) if options[:n] wwid.add_item(title.cap_first, "Later", {:note => note, :back => date}) wwid.write(wwid.doing_file) elsif STDIN.stat.size > 0 title, note = wwid.format_input(STDIN.read) note.push(options[:n]) if options[:n] wwid.add_item(title.cap_first, "Later", {:note => note, :back => date}) wwid.write(wwid.doing_file) else raise "You must provide content when creating a new entry" end end end end desc 'Add a completed item with @done(date). No argument finishes last entry.' arg_name 'entry' command :done do |c| c.desc 'Remove @done tag' c.default_value false c.switch [:r,:remove], :negatable => false, :default_value => false c.desc 'Include date' c.default_value true c.switch [:d,:date], :negatable => true, :default_value => true c.desc 'Immediately archive the entry' c.default_value false c.switch [:a,:archive], :negatable => false, :default_value => false # TODO: Set @done date to specific time, backtracking start date in case of --took option. Should ignore --back option if present. # c.desc 'Set finish date to specific date/time (natural langauge)' # c.flag [:at] c.desc 'Backdate start date by interval [4pm|20m|2h|yesterday noon]' c.flag [:b,:back] c.desc 'Set completion date to start date plus interval (XX[mhd] or HH:MM)' c.long_desc 'If used without the --back option, the start date will be moved back to allow the completion date to be the current time.' c.flag [:t,:took] c.desc 'Section' c.default_value wwid.current_section c.flag [:s,:section], :default_value => wwid.current_section c.desc "Edit entry with #{ENV['EDITOR']}" c.switch [:e,:editor] # c.desc "Edit entry with specified app" # c.arg_name 'editor_app' # c.default_value wwid.config.has_key?('editor_app') && wwid.config['editor_app'] ? wwid.config['editor_app'] : false # c.flag [:a,:app] c.action do |global_options,options,args| took = 0 if options[:took] took = wwid.chronify_qty(options[:took]) raise "Unable to parse date string for --took" if took.nil? end if options[:back] date = wwid.chronify(options[:back]) raise "Unable to parse date string for --back" if date.nil? else date = options[:took] ? Time.now - took : Time.now end if options[:took] finish_date = date + took elsif options[:back] finish_date = date else finish_date = Time.now end section = wwid.guess_section(options[:s]) || options[:s].cap_first donedate = options[:d] ? "(#{finish_date.strftime('%F %R')})" : "" if options[:e] raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil? input = "" input += args.join(" ") if args.length > 0 input = wwid.fork_editor(input) if input title, note = wwid.format_input(input) title += " @done#{donedate}" section = "Archive" if options[:a] wwid.add_item(title.cap_first, section.cap_first, {:note => note, :back => date}) wwid.write(wwid.doing_file) else raise "No content" end elsif args.length == 0 && STDIN.stat.size == 0 if options[:r] wwid.tag_last({:tags => ["done"], :count => 1, :section => section, :remove => true }) else wwid.tag_last({:tags => ["done"], :count => 1, :section => section, :archive => options[:a], :back => finish_date, :date => options[:d]}) end else if args.length > 0 title, note = wwid.format_input(args.join(" ")) title.chomp! title += " @done#{donedate}" section = "Archive" if options[:a] wwid.add_item(title.cap_first, section.cap_first, {:note => note, :back => date}) wwid.write(wwid.doing_file) elsif STDIN.stat.size > 0 title, note = wwid.format_input(STDIN.read) title += " @done#{donedate}" section = options[:a] ? "Archive" : section wwid.add_item(title.cap_first, section.cap_first, {:note => note, :back => date}) wwid.write(wwid.doing_file) else raise "You must provide content when creating a new entry" end end end end desc 'Mark last X entries as @done' long_desc 'Marks the last X entries with a @done tag and current date. Does not alter already completed entries.' arg_name 'count' command :finish do |c| c.desc 'Include date' c.default_value true c.switch [:d,:date], :default_value => true c.desc 'Backdate completed date to date string [4pm|20m|2h|yesterday noon]' c.flag [:b,:back] c.desc 'Set the completed date to the start date plus XX[hmd]' c.flag [:t,:took] c.desc 'Auto-generate finish dates from next entry\'s start time' c.long_desc 'Automatically generate completion dates 1 minute before next start date. --auto overrides the --date and --back parameters.' c.default_value false c.switch [:auto], :negatable => false, :default_value => false c.desc 'Archive entries' c.default_value false c.switch [:a,:archive], :negatable => false, :default_value => false c.desc 'Section' c.default_value wwid.current_section c.flag [:s,:section], :default_value => wwid.current_section c.action do |global_options,options,args| section = wwid.guess_section(options[:s]) || options[:s].cap_first unless options[:auto] raise "--back and --took cannot be used together" if options[:back] and options[:took] if options[:back] date = wwid.chronify(options[:back]) raise "Unable to parse date string" if date.nil? elsif options[:took] date = wwid.chronify_qty(options[:took]) else date = Time.now end end if args.length > 1 raise "Only one argument allowed" elsif args.length == 0 || args[0] =~ /\d+/ count = args[0] ? args[0].to_i : 1 wwid.tag_last({:tags => ["done"], :count => count, :section => section, :archive => options[:a], :sequential => options[:auto], :date => options[:d], :back => date }) else raise "Invalid argument (specify number of recent items to mark @done)" end end end desc 'Tag last entry' arg_name 'tag1 [tag2...]' command :tag do |c| c.desc 'Section' c.default_value wwid.current_section c.flag [:s,:section], :default_value => wwid.current_section c.desc 'How many recent entries to tag (0 for all)' c.default_value 1 c.flag [:c,:count], :default_value => 1 c.desc 'Include current date/time with tag' c.default_value false c.switch [:d,:date], :negatable => false, :default_value => false c.desc 'Remove given tag(s)' c.default_value false c.switch [:r,:remove], :negatable => false, :default_value => false c.desc 'Autotag entries based on autotag configuration in ~/.doingrc' c.default_value false c.switch [:a,:autotag], :negatable => false, :default_value => false c.action do |global_options,options,args| if args.length == 0 && !options[:a] raise "You must specify at least one tag" else section = wwid.guess_section(options[:s]) || options[:s].cap_first unless options[:a] if args.join("") =~ /,/ tags = args.join("").split(/,/) else tags = args.join(" ").split(" ") # in case tags are quoted as one arg end tags.map!{|tag| tag.sub(/^@/,'').strip } else tags = [] end count = options[:c].to_i if count == 0 if options[:a] print "Are you sure you want to autotag all records? (y/N) " elsif options[:r] print "Are you sure you want to remove #{tags.join(" and ")} from all records? (y/N) " else print "Are you sure you want to add #{tags.join(" and ")} to all records? (y/N) " end res = STDIN.gets unless res.strip =~ /^y(es)?$/i raise "Cancelled" end end wwid.tag_last({:tags => tags, :count => count, :section => section, :date => options[:d], :remove => options[:r], :autotag => options[:a]}) end end end desc 'Mark last entry as highlighted' command :mark do |c| c.desc 'Section' c.default_value wwid.current_section c.flag [:s,:section], :default_value => wwid.current_section c.desc 'Remove mark' c.default_value false c.switch [:r,:remove], :negatable => false, :default_value => false c.action do |global_options,options,args| mark = wwid.config['marker_tag'] || "flagged" wwid.tag_last({:tags => [mark], :section => options[:s], :remove => options[:r]}) end end desc 'List all entries' long_desc 'The argument can be a section name, tag(s) or both. "pick" or "choose" as an argument will offer a section menu.' arg_name 'section [tags]' command :show do |c| c.desc 'Tag boolean (AND,OR,NONE)' c.default_value "OR" c.flag [:b,:boolean], :default_value => "OR" c.desc 'Max count to show' c.default_value 0 c.flag [:c,:count], :default_value => 0 c.desc 'Age (oldest/newest)' c.default_value 'newest' c.flag [:a,:age], :default_value => 'newest' c.desc 'Sort order (asc/desc)' c.default_value 'asc' c.flag [:s,:sort], :default_value => 'asc' c.desc 'Date range to show, or a single day to filter date on.' c.long_desc 'Date range argument should be quoted. Date specifications can be natural language. To specify a range, use "to," or "through,".\n\ndoing show --from "monday to friday"' c.flag [:f,:from] c.desc 'Show time intervals on @done tasks' c.default_value true c.switch [:t,:times], :default_value => true c.desc 'Show intervals with totals at the end of output' c.default_value false c.switch [:totals], :default_value => false, :negatable => true c.desc 'Only show items with recorded time intervals' c.default_value false c.switch [:only_timed], :default_value => false, :negatable => false c.desc 'Output to export format (csv|html|json)' c.flag [:o,:output] c.action do |global_options,options,args| tag_filter = false tags = [] if args.length > 0 if args[0] =~ /^all$/i section = "All" args.shift elsif args[0] =~ /^(choose|pick)$/i section = wwid.choose_section args.shift elsif args[0] =~ /^@/ section = "All" else section = wwid.guess_section(args[0]) raise "No such section: #{args[0]}" unless section args.shift end if args.length > 0 args.each {|arg| if arg =~ /,/ arg.split(/,/).each {|tag| tags.push(tag.strip.sub(/^@/,'')) } else tags.push(arg.strip.sub(/^@/,'')) end } end else section = wwid.current_section end unless tags.empty? tag_filter = { 'tags' => tags, 'bool' => options[:b] } end if options[:f] date_string = options[:f] if date_string =~ / (to|through|thru|(un)?til|-+) / dates = date_string.split(/ (to|through|thru|(un)?til|-+) /) start = wwid.chronify(dates[0]) finish = wwid.chronify(dates[2]) else start = wwid.chronify(date_string) finish = false end exit_now! "Unrecognized date string" unless start dates = [start,finish] end options[:t] = true if options[:totals] puts wwid.list_section({:section => section, :date_filter => dates, :count => options[:c].to_i, :tag_filter => tag_filter, :age => options[:a], :order => options[:s], :output => options[:output], :times => options[:t], :totals => options[:totals], :highlight => true, :only_timed => options[:only_timed]}) end end desc 'Search for entries' long_desc 'Search all sections (or limit to a single section) for entries matching text or regular expression.' arg_name 'search_pattern' command :grep do |c| c.desc 'Section' c.default_value "all" c.flag [:s,:section], :default_value => "All" c.desc 'Output to export format (csv|html|json)' c.flag [:o,:output] c.desc 'Show time intervals on @done tasks' c.default_value true c.switch [:t,:times], :default_value => true c.desc 'Show intervals with totals at the end of output' c.default_value false c.switch [:totals], :default_value => false, :negatable => true c.desc 'Only show items with recorded time intervals' c.default_value false c.switch [:only_timed], :default_value => false, :negatable => false c.action do |global_options,options,args| section = wwid.guess_section(options[:s]) if options[:s] options[:t] = true if options[:totals] puts wwid.list_section({:search => args.join(' '), :section => section, :output => options[:output], :times => options[:t], :highlight => true, :totals => options[:totals], :only_timed => options[:only_timed]}) end end desc 'List recent entries' default_value 10 arg_name 'count' command :recent do |c| c.desc 'Section' c.default_value wwid.current_section c.flag [:s,:section], :default_value => wwid.current_section c.desc 'Show time intervals on @done tasks' c.default_value true c.switch [:t,:times], :default_value => true c.desc 'Show intervals with totals at the end of output' c.default_value false c.switch [:totals], :default_value => false, :negatable => true c.action do |global_options,options,args| section = wwid.guess_section(options[:s]) || options[:s].cap_first unless global_options[:version] if args.length > 0 count = args[0].to_i else count = 10 end options[:t] = true if options[:totals] puts wwid.recent(count,section.cap_first,{ :times => options[:t], :totals => options[:totals] }) end end end desc 'List entries from today' command :today do |c| c.desc 'Show time intervals on @done tasks' c.default_value true c.switch [:t,:times], :default_value => true c.desc 'Show time totals at the end of output' c.default_value false c.switch [:totals], :default_value => false, :negatable => true c.desc 'Output to export format (csv|html|json)' c.flag [:o,:output] c.action do |global_options,options,args| options[:t] = true if options[:totals] puts wwid.today(options[:t],options[:output],{:totals => options[:totals]}).chomp end end desc 'List entries for a date' long_desc 'Date argument can be natural language. "thursday" would be interpreted as "last thursday," and "2d" would be interpreted as "two days ago." If you use "to" or "through" between two dates, it will create a range.' arg_name 'date_string' command :on do |c| c.desc 'Section' c.arg_name 'section_name' c.default_value 'All' c.flag [:s,:section], :default_value => 'All' c.desc 'Show time intervals on @done tasks' c.default_value true c.switch [:t,:times], :default_value => true c.desc 'Show time totals at the end of output' c.default_value false c.switch [:totals], :default_value => false, :negatable => true c.desc 'Output to export format (csv|html|json)' c.flag [:o,:output] c.action do |global_options,options,args| date_string = args.join(" ") if date_string =~ / (to|through|thru) / dates = date_string.split(/ (to|through|thru) /) start = wwid.chronify(dates[0]) finish = wwid.chronify(dates[2]) else start = wwid.chronify(date_string) finish = false end exit_now! "Unrecognized date string" unless start message = "Date interpreted as #{start}" message += " to #{finish}" if finish wwid.results.push(message) options[:t] = true if options[:totals] puts wwid.list_date([start,finish],options[:s],options[:t],options[:output],{:totals => options[:totals]}).chomp end end desc 'List entries from yesterday' default_value wwid.current_section arg_name 'section' command :yesterday do |c| c.desc 'Output to export format (csv|html|json)' c.flag [:o,:output] c.desc 'Show time intervals on @done tasks' c.default_value true c.switch [:t,:times], :default_value => true c.desc 'Show time totals at the end of output' c.default_value false c.switch [:totals], :default_value => false, :negatable => true c.action do |global_options, options,args| section = args.length > 0 ? args.join(" ") : wwid.current_section puts wwid.yesterday(section,options[:t],options[:o],{:totals => options[:totals]}).chomp end end desc 'Show the last entry' command :last do |c| c.action do |global_options,options,args| puts wwid.last.strip end end desc 'List sections' command :sections do |c| c.action do |global_options,options,args| puts wwid.sections.join("\t") end end desc 'Select a section to display from a menu' command :choose do |c| c.action do |global_options,options,args| section = wwid.choose_section puts wwid.list_section({:section => section.cap_first, :count => 0}) end end desc 'Add a new section to the "doing" file' arg_name 'section_name' command :add_section do |c| c.action do |global_options,options,args| unless wwid.sections.include?(args[0]) wwid.add_section(args[0].cap_first) wwid.write(wwid.doing_file) else raise "Section #{args[0]} already exists" end end end desc 'List available color variables for configuration templates and views' command :colors do |c| c.action do |global_options,options,args| clrs = wwid.colors bgs = [] fgs = [] clrs.each {|k,v| if k =~ /bg/ bgs.push("#{v} #{clrs['default']} <-- #{k}") else fgs.push("#{v}XXXX#{clrs['default']} <-- #{k}") end } puts fgs.join("\n") puts bgs.join("\n") end end desc 'Display a user-created view' arg_name 'view_name' command :view do |c| c.desc 'Section (override view settings)' c.flag [:s,:section] c.desc 'Count to display (override view settings)' c.flag [:c,:count], :must_match => /^\d+$/, :type => Integer c.desc 'Output to export format (csv|html|json)' c.flag [:o,:output] c.desc 'Show time intervals on @done tasks' c.default_value true c.switch [:t,:times], :default_value => true c.desc 'Show intervals with totals at the end of output' c.default_value false c.switch [:totals], :default_value => false, :negatable => true c.desc 'Only show items with recorded time intervals' c.default_value false c.switch [:only_timed], :default_value => false, :negatable => true c.action do |global_options,options,args| if args.empty? title = wwid.choose_view else title = wwid.guess_view(args[0]) end if options[:s] section = wwid.guess_section(options[:s]) || options[:s].cap_first end view = wwid.get_view(title) if view if (view.has_key?('only_timed') && view['only_timed']) || options[:only_timed] only_timed = true else only_timed = false end template = view.has_key?('template') ? view['template'] : nil format = view.has_key?('date_format') ? view['date_format'] : nil tags_color = view.has_key?('tags_color') ? view['tags_color'] : nil tag_filter = false if view.has_key?('tags') unless view['tags'].nil? || view['tags'].empty? tag_filter = {'tags' => [], 'bool' => "OR"} if view['tags'].class == Array tag_filter['tags'] = view['tags'].map{|tag| tag.strip } else tag_filter['tags'] = view['tags'].gsub(/[, ]+/," ").split(" ").map{|tag| tag.strip } end tag_filter['bool'] = view.has_key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].upcase : "OR" end end # If the -o/--output flag was specified, override any default in the view template options[:o] ||= view.has_key?('output_format') ? view['output_format'] : "template" count = options[:c] ? options[:c] : view.has_key?('count') ? view['count'] : 10 section = options[:s] ? section : view.has_key?('section') ? view['section'] : wwid.current_section order = view.has_key?('order') ? view['order'] : "asc" options[:t] = true if options[:totals] options[:output].downcase! if options[:output] puts wwid.list_section({:section => section, :count => count, :template => template, :format => format, :order => order, :tag_filter => tag_filter, :output => options[:o], :tags_color => tags_color, :times => options[:t], :highlight => true, :totals => options[:totals], :only_timed => only_timed }) else if title.class == FalseClass exit_now! "Cancelled" else raise "View #{title} not found in config" end end end end desc 'List available custom views' command :views do |c| c.action do |global_options,options,args| puts wwid.views.join("\t") end end desc 'Move entries in between sections' arg_name 'section' default_value wwid.current_section command :archive do |c| c.desc 'Count to keep (ignored if archiving by tag)' c.default_value 5 c.flag [:k,:keep], :default_value => 5, :must_match => /^\d+$/, :type => Integer c.desc 'Move entries to' c.default_value "Archive" c.flag [:t,:to], :default_value => "Archive" c.desc 'Tag boolean' c.default_value "AND" c.flag [:b,:bool], :default_value => "AND" c.action do |global_options,options,args| if args.length > 0 if args[0] =~ /^@\S+/ section = "all" tags = args.map {|t| t.sub(/^@/,'').strip } else section = args[0].cap_first tags = args.length > 1 ? args[1..-1].map {|t| t.sub(/^@/,'').strip } : nil end else section = wwid.current_section tags = nil end wwid.archive(section,options[:k],options[:t],tags,options[:b]) end end desc 'Open the "doing" file in an editor' command :open do |c| if `uname` =~ /Darwin/ c.desc 'open with app name' c.arg_name 'app_name' c.flag [:a] c.desc 'open with app bundle id' c.arg_name 'bundle_id' c.flag [:b] end c.desc 'open with $EDITOR' c.switch [:e], :negatable => false c.action do |global_options,options,args| params = options.dup params.delete_if { |k,v| k.class == String || v.nil? || v == false } if `uname` =~ /Darwin/ if params.length < 2 if options[:a] system %Q{open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}"} elsif options[:b] system %Q{open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}"} elsif options[:e] raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil? system %Q{$EDITOR "#{File.expand_path(wwid.doing_file)}"} else if wwid.config.has_key?('editor_app') && !wwid.config['editor_app'].nil? system %Q{open -a "#{wwid.config['editor_app']}" "#{File.expand_path(wwid.doing_file)}"} else system %Q{open "#{File.expand_path(wwid.doing_file)}"} end end else raise "The open command takes a single parameter. #{params.length} specified." end else raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil? system %Q{$EDITOR "#{File.expand_path(wwid.doing_file)}"} end end end desc 'Edit the configuration file' command :config do |c| c.desc 'Editor to use' c.default_value ENV['EDITOR'] c.flag [:e,:editor], :default_value => nil if `uname` =~ /Darwins/ c.desc 'Application to use' c.flag [:a] c.desc "Use the editor_app defined in ~/.doingrc (#{wwid.config['editor_app']})" c.switch [:x] c.desc 'Application bundle id to use' c.flag [:b] end c.action do |global_options,options,args| if `uname` =~ /Darwins/ if options[:x] %x{open -a "#{wwid.config['editor_app']}" "#{File.join(Dir.home, wwid.default_config_file)}"} elsif options[:a] || options[:b] if options[:a] %x{open -a "#{options[:a]}" "#{File.join(Dir.home, wwid.default_config_file)}"} elsif options[:b] %x{open -b #{options[:b]} "#{File.join(Dir.home, wwid.default_config_file)}"} end else raise "No EDITOR variable defined in environment" if options[:e].nil? && ENV['EDITOR'].nil? editor = options[:e].nil? ? ENV['EDITOR'] : options[:e] system %Q{#{editor} "#{File.join(Dir.home, wwid.default_config_file)}"} end else raise "No EDITOR variable defined in environment" if options[:e].nil? && ENV['EDITOR'].nil? editor = options[:e].nil? ? ENV['EDITOR'] : options[:e] system %Q{#{editor} "#{File.join(Dir.home, wwid.default_config_file)}"} end end end desc 'Undo the last change to the doing_file' command :undo do |c| c.desc 'Specify alternate doing file' c.default_value wwid.doing_file c.flag [:f,:file], :default_value => wwid.doing_file c.action do |global_options,options,args| file = options[:f] || wwid.doing_file wwid.restore_backup(file) end end pre do |global,command,options,args| if global[:doing_file] wwid.init_doing_file(global[:doing_file]) else wwid.init_doing_file end wwid.config[:include_notes] = false unless global[:notes] if global[:version] $stdout.puts "doing v" + Doing::VERSION end # Return true to proceed; false to abort and not call the # chosen command # Use skips_pre before a command to skip this block # on that command only true end post do |global,command,options,args| # Use skips_post before a command to skip this # block on that command only $stderr.puts wwid.results.join("\n") end on_error do |exception| # puts exception.message true end exit run(ARGV)