Class: ICalPal::Options
- Inherits:
-
Object
- Object
- ICalPal::Options
- Defined in:
- lib/options.rb
Overview
Handle program options from all sources:
-
Defaults
-
Environment variables
-
Configuration file
-
Command-line arguments
Many options are intentionally copied from icalBuddy. Note that icalpal requires two hyphens for all options, except single-letter options which require a single hyphen.
Options can be abbreviated as long as they are unique.
Constant Summary collapse
- COMMANDS =
Commands that can be run
%w{events eventsToday eventsNow tasks datedTasks undatedTasks calendars accounts stores}
- OUTFORMATS =
Supported output formats
%w{ansi csv default hash html json md rdoc remind toc xml yaml}
Instance Method Summary collapse
-
#initialize ⇒ Options
constructor
Define the OptionParser.
-
#parse_options ⇒ Hash
Parse options from the CLI and merge them with other sources.
Constructor Details
#initialize ⇒ Options
Define the OptionParser
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/options.rb', line 22 def initialize # prologue @op = OptionParser.new @op.summary_width = 23 @op. += " [-c] COMMAND" @op.version = ICalPal::VERSION @op.accept(ICalPal::RDT) { |s| ICalPal::RDT.conv(s) } # head @op.on_head("\nCOMMAND must be one of the following:\n\n") @op.on("%s%s %sPrint events" % pad('events')) @op.on("%s%s %sPrint tasks" % pad('tasks')) @op.on("%s%s %sPrint calendars" % pad('calendars')) @op.on("%s%s %sPrint accounts" % pad('accounts')) @op.separator('') @op.on("%s%s %sPrint events occurring today" % pad('eventsToday')) @op.on("%s%s %sPrint events occurring between today and NUM days into the future" % pad('eventsToday+NUM')) @op.on("%s%s %sPrint events occurring at present time" % pad('eventsNow')) @op.on("%s%s %sPrint tasks with a due date" % pad('datedTasks')) @op.on("%s%s %sPrint tasks with no due date" % pad('undatedTasks')) # global @op.separator("\nGlobal options:\n\n") @op.on('-c=COMMAND', '--cmd=COMMAND', COMMANDS, 'Command to run') @op.on('--db=DB', "Use DB file instead of Calendar", "(default: #{$defaults[:common][:db]}", 'For the tasks commands this should be a directory containing .sqlite files', "(default: #{$defaults[:tasks][:db]})") @op.on('--cf=FILE', "Set config file path (default: #{$defaults[:common][:cf]})") @op.on('-o', '--output=FORMAT', OUTFORMATS, "Print as FORMAT (default: #{$defaults[:common][:output]})", "[#{OUTFORMATS.join(', ')}]") # include/exclude @op.separator("\nIncluding/excluding accounts, calendars, items:\n\n") @op.on('--is=ACCOUNTS', Array, 'List of accounts to include') @op.on('--es=ACCOUNTS', Array, 'List of accounts to exclude') @op.separator('') @op.on('--it=TYPES', Array, 'List of calendar types to include') @op.on('--et=TYPES', Array, 'List of calendar types to exclude', "[#{EventKit::EKSourceType.map { |i| i[:name] }.join(', ') }]") @op.separator('') @op.on('--ic=CALENDARS', Array, 'List of calendars to include') @op.on('--ec=CALENDARS', Array, 'List of calendars to exclude') @op.separator('') @op.on('--il=LISTS', Array, 'List of reminder lists to include') @op.on('--el=LISTS', Array, 'List of reminder lists to exclude') @op.separator('') @op.on('--match=FIELD=REGEXP', String, 'Include only items whose FIELD matches REGEXP (ignoring case)') # dates @op.separator("\nChoosing dates:\n\n") @op.on('--from=DATE', ICalPal::RDT, 'List events starting on or after DATE') @op.on('--to=DATE', ICalPal::RDT, 'List events starting on or before DATE', 'DATE can be yesterday, today, tomorrow, +N, -N, or anything accepted by DateTime.parse()', 'See https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html#method-c-parse') @op.separator('') @op.on('-n', 'Include only events from now on') @op.on('--days=N', OptionParser::DecimalInteger, 'Show N days of events, including start date') @op.on('--sed', 'Show empty dates with --sd') @op.on('--ia', 'Include only all-day events') @op.on('--ea', 'Exclude all-day events') # properties @op.separator("\nChoose properties to include in the output:\n\n") @op.on('--iep=PROPERTIES', Array, 'List of properties to include') @op.on('--eep=PROPERTIES', Array, 'List of properties to exclude') @op.on('--aep=PROPERTIES', Array, 'List of properties to include in addition to the default list') @op.separator('') @op.on('--itp=PROPERTIES', Array, 'List of task properties to include') @op.on('--etp=PROPERTIES', Array, 'List of task properties to exclude') @op.on('--atp=PROPERTIES', Array, 'List of task properties to include in addition to the default list', 'Included for backwards compatability, these are aliases for --iep, --eep, and --aep') @op.separator('') @op.on('--uid', 'Show event UIDs') @op.on('--eed', 'Exclude end datetimes') @op.separator('') @op.on('--nc', 'No calendar names') @op.on('--npn', 'No property names') @op.on('--nrd', 'No relative dates') @op.separator('') @op.separator(@op.summary_indent + 'Properties are listed in the order specified') @op.separator('') @op.separator(@op.summary_indent + "Use 'all' for PROPERTIES to include all available properties (except any listed in --eep)") @op.separator(@op.summary_indent + "Use 'list' for PROPERTIES to list all available properties and exit") # formatting @op.separator("\nFormatting the output:\n\n") @op.on('--li=N', OptionParser::DecimalInteger, 'Show at most N items (default: 0 for no limit)') @op.separator('') @op.on('--sc', 'Separate by calendar') @op.on('--sd', 'Separate by date') @op.on('--sp', 'Separate by priority') @op.on('--sep=PROPERTY', 'Separate by PROPERTY') @op.separator('') @op.on('--sort=PROPERTY', 'Sort by PROPERTY') @op.on('--std', 'Sort tasks by due date (same as --sort=due_date)') @op.on('--stda', 'Sort tasks by due date (ascending) (same as --sort=due_date -r)') @op.on('-r', '--reverse', 'Sort in reverse') @op.separator('') @op.on('--ps=SEPARATORS', Array, 'List of property separators') @op.on('--ss=SEPARATOR', String, 'Set section separator') @op.separator('') @op.on('--df=FORMAT', String, 'Set date format') @op.on('--tf=FORMAT', String, 'Set time format', 'See https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html#method-i-strftime for details') @op.separator('') @op.on('-b', '--bullet=STRING', String, 'Use STRING for bullets') @op.on('--ab=STRING', String, 'Use STRING for alert bullets') @op.on('--nb', 'Do not use bullets') @op.on('--nnr=SEPARATOR', String, 'Set replacement for newlines within notes') @op.separator('') @op.on('-f', 'Format output using standard ANSI colors') @op.on('--color', 'Format output using a larger color palette') # help @op.separator("\nHelp:\n\n") @op.on('-h', '--help', 'Show this message') { @op.abort(@op.help) } @op.on('-V', '-v', '--version', "Show version and exit (#{@op.version})") { @op.abort(@op.version) } @op.on('-d', '--debug=LEVEL', /#{Regexp.union(Logger::SEV_LABEL[0..-2]).source}/i, "Set the logging level (default: #{Logger::SEV_LABEL[$defaults[:common][:debug]].downcase})", "[#{Logger::SEV_LABEL[0..-2].join(', ').downcase}]") # environment variables @op.on_tail("\nEnvironment variables:\n\n") @op.on_tail("%s%s %sAdditional arguments" % pad('ICALPAL')) @op.on_tail("%s%s %sAdditional arguments from a file" % pad('ICALPAL_CONFIG')) @op.on_tail("%s%s %s(default: #{$defaults[:common][:cf]})" % pad('')) end |
Instance Method Details
#parse_options ⇒ Hash
Parse options from the CLI and merge them with other sources
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/options.rb', line 180 def begin cli = {} env = {} cf = {} # Load from CLI, environment, configuration file @op.parse!(into: cli) @op.parse!(ENV['ICALPAL'].split, into: env) rescue nil cli[:cf] ||= ENV['ICALPAL_CONFIG'] || $defaults[:common][:cf] @op.parse!(File.read(File.(cli[:cf])).split, into: cf) rescue nil cli[:cmd] ||= @op.default_argv[0] cli[:cmd] ||= env[:cmd] if env[:cmd] cli[:cmd] ||= cf[:cmd] if cf[:cmd] cli[:cmd] = 'stores' if cli[:cmd] == 'accounts' # Parse eventsNow and eventsToday commands cli[:cmd].match('events(Now|Today)(\+[0-9]+)?') do |m| cli[:n] = true if m[1] == 'Now' cli[:days] = (m[1] == 'Today')? m[2].to_i + 1 : 1 cli[:from] = $today cli[:to] = $today + cli[:days] cli[:days] = Integer(cli[:to] - cli[:from]) cli[:cmd] = 'events' end if cli[:cmd] # Must have a valid command raise(OptionParser::MissingArgument, 'COMMAND is required') unless cli[:cmd] raise(OptionParser::InvalidArgument, "Unknown COMMAND #{cli[:cmd]}") unless (COMMANDS.any? cli[:cmd]) # Merge options opts = $defaults[:common] .merge($defaults[cli[:cmd].to_sym]) .merge(cf) .merge(env) .merge(cli) # datedTasks and undatedTasks opts[:cmd] = "tasks" if opts[:cmd] == "datedTasks" opts[:cmd] = "tasks" if opts[:cmd] == "undatedTasks" # Make sure opts[:db] and opts[:tasks] are Arrays opts[:db] = [ opts[:db] ] unless opts[:db].is_a?(Array) opts[:tasks] = [ opts[:tasks] ] unless opts[:db].is_a?(Array) # All kids love log! $log.level = opts[:debug] # For posterity opts[:ruby] = RUBY_VERSION opts[:version] = @op.version # From the Department of Redundancy Department opts[:iep] += opts[:itp] if opts[:itp] opts[:eep] += opts[:etp] if opts[:etp] opts[:aep] += opts[:atp] if opts[:atp] opts[:props] = (opts[:iep] + opts[:aep] - opts[:eep]).uniq # From, to, days if opts[:from] opts[:to] += 1 if opts[:to] opts[:to] ||= opts[:from] + 1 if opts[:from] opts[:to] = opts[:from] + opts[:days] if opts[:days] opts[:days] ||= Integer(opts[:to] - opts[:from]) opts[:from] = $now if opts[:n] end # Sorting opts[:sort] = 'due_date' if opts[:std] or opts[:stda] opts[:reverse] = true if opts[:std] # Colors opts[:palette] = 8 if opts[:f] opts[:palette] = 24 if opts[:color] # Sections unless opts[:sep] opts[:sep] = 'calendar' if opts[:sc] opts[:sep] = 'sday' if opts[:sd] opts[:sep] = 'long_priority' if opts[:sp] end opts[:nc] = true if opts[:sc] # Sanity checks raise(OptionParser::InvalidArgument, '--li cannot be negative') if opts[:li].negative? raise(OptionParser::InvalidOption, 'Start date must be before end date') if opts[:from] && opts[:from] > opts[:to] raise(OptionParser::MissingArgument, 'No properties to display') if opts[:props].empty? rescue StandardError => e @op.abort("#{e}\n\n#{@op.help}\n#{e}") end opts.sort.to_h end |