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 calendars accounts stores}
- OUTFORMATS =
Supported output formats
%w{ansi csv default hash html json md rdoc toc yaml remind}
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
21 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 |
# File 'lib/options.rb', line 21 def initialize # prologue @op = OptionParser.new @op.summary_width = 23 @op. += " [-c] COMMAND" @op.version = '1.2.0' @op.accept(ICalPal::RDT) { |s| ICalPal::RDT.conv(s) } # head @op.on("\nCOMMAND must be one of the following:\n\n") @op.on("%s%s %sPrint events" % pad('events')) @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')) # 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') @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 calendars:\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') # 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') # @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('--sta', 'Sort tasks by due date (ascending)') # @op.on('--std', 'Sort tasks by due date (descending)') # @op.separator('') @op.on('--sep=PROPERTY', 'Separate by PROPERTY') @op.separator('') @op.on('--sort=PROPERTY', 'Sort by PROPERTY') @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('--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
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 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 |
# File 'lib/options.rb', line 165 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) # All kids love log! $log.level = opts[:debug] # From the Department of Redundancy Department 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 # 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] = '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? # Open the database here so we can catch errors and print the help message $log.debug("Opening database: #{opts[:db]}") $db = SQLite3::Database.new(opts[:db], { readonly: true, results_as_hash: true }) $db.prepare('SELECT 1 FROM Calendar LIMIT 1').close rescue SQLite3::SQLException => e @op.abort("#{opts[:db]} is not a Calendar database") rescue SQLite3::Exception => e @op.abort("#{opts[:db]}: #{e}") rescue StandardError => e @op.abort("#{e}\n\n#{@op.help}\n#{e}") end opts.sort.to_h end |