lewt 0.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.md +22 -0
- data/README.md +238 -0
- data/bin/lewt +10 -0
- data/lib/config/customers.yml +33 -0
- data/lib/config/enterprise.yml +54 -0
- data/lib/config/settings.yml +10 -0
- data/lib/config/templates/invoice.html.liquid +63 -0
- data/lib/config/templates/invoice.text.liquid +40 -0
- data/lib/config/templates/meta.html.liquid +0 -0
- data/lib/config/templates/meta.text.liquid +1 -0
- data/lib/config/templates/metastat.html.liquid +2 -0
- data/lib/config/templates/metastat.text.liquid +8 -0
- data/lib/config/templates/report.html.liquid +29 -0
- data/lib/config/templates/report.text.liquid +15 -0
- data/lib/config/templates/style.css +461 -0
- data/lib/extension.rb +158 -0
- data/lib/extensions/calendar-timekeeping/apple_extractor.rb +63 -0
- data/lib/extensions/calendar-timekeeping/calendar-timekeeping.rb +65 -0
- data/lib/extensions/calendar-timekeeping/extractor.rb +62 -0
- data/lib/extensions/calendar-timekeeping/gcal_extractor.rb +61 -0
- data/lib/extensions/calendar-timekeeping/ical_extractor.rb +52 -0
- data/lib/extensions/liquid-renderer.rb +106 -0
- data/lib/extensions/metastat/metamath.rb +108 -0
- data/lib/extensions/metastat/metastat.rb +161 -0
- data/lib/extensions/simple-expenses.rb +112 -0
- data/lib/extensions/simple-invoices.rb +93 -0
- data/lib/extensions/simple-milestones.rb +102 -0
- data/lib/extensions/simple-reports.rb +81 -0
- data/lib/extensions/store.rb +81 -0
- data/lib/lewt.rb +233 -0
- data/lib/lewt_book.rb +29 -0
- data/lib/lewt_ledger.rb +149 -0
- data/lib/lewtopts.rb +170 -0
- data/tests/LEWT Schedule.ics +614 -0
- data/tests/expenses.csv +1 -0
- data/tests/milestones.csv +1 -0
- data/tests/run_tests.rb +14 -0
- data/tests/tc_Billing.rb +29 -0
- data/tests/tc_CalExt.rb +44 -0
- data/tests/tc_Lewt.rb +37 -0
- data/tests/tc_LewtExtension.rb +31 -0
- data/tests/tc_LewtLedger.rb +38 -0
- data/tests/tc_LewtOpts.rb +26 -0
- metadata +158 -0
data/lib/lewt.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'date'
|
4
|
+
require 'yaml'
|
5
|
+
require 'optparse'
|
6
|
+
require_relative 'lewtopts.rb'
|
7
|
+
require_relative 'extension.rb'
|
8
|
+
require_relative 'lewt_book.rb'
|
9
|
+
require_relative 'lewt_ledger.rb'
|
10
|
+
|
11
|
+
|
12
|
+
# Author:: Jason Wijegooneratne (mailto:code@jwije.com)
|
13
|
+
# Copyright:: Copyright (c) 2014 Jason Wijegooneratne
|
14
|
+
# License:: MIT. See LICENSE.md distributed with the source code for more information.
|
15
|
+
|
16
|
+
# This module acts as a container for the LEWT namespace
|
17
|
+
module LEWT
|
18
|
+
|
19
|
+
VERSION = "0.5.12"
|
20
|
+
|
21
|
+
# The Lewt class contains the major functionality of this program.
|
22
|
+
# It handles loading all extensions, gathering the results and passing options ariound the place.
|
23
|
+
# It also works quite closely with the LewtExtension and LewtOpts classes.
|
24
|
+
class Lewt
|
25
|
+
|
26
|
+
# Can be used to split strings passed to lewt through CL optios ie:
|
27
|
+
# "acme,wayne_corp".split(CLIENT_SPLIT_REGEX) # get each argument for client mathching
|
28
|
+
OPTION_DELIMITER_REGEX = /[,+:]/
|
29
|
+
|
30
|
+
# This matches symbols passed thought command ie: -p meta-stat => meta_stat
|
31
|
+
OPTION_SYMBOL_REGEX = /\W/
|
32
|
+
|
33
|
+
def initialize( library_options = nil )
|
34
|
+
core_settings = YAML.load_file( File.expand_path( '../config/settings.yml', __FILE__) )
|
35
|
+
if File.exists? File.expand_path( '~/.lewt_settings', __FILE__)
|
36
|
+
core_settings.merge! YAML.load_file( File.expand_path( '~/.lewt_settings', __FILE__) )
|
37
|
+
end
|
38
|
+
|
39
|
+
@lewt_stash = core_settings['lewt_stash'] || File.expand_path('../', __FILE__) + "/config/"
|
40
|
+
@settings = YAML.load_file( @lewt_stash + 'settings.yml' )
|
41
|
+
|
42
|
+
# Start by loading the local config files
|
43
|
+
@customers = YAML.load_file(@lewt_stash + "customers.yml")
|
44
|
+
@enterprise = YAML.load_file(@lewt_stash + "enterprise.yml")
|
45
|
+
|
46
|
+
# Referenceall registered extension for later invocation
|
47
|
+
@extensions = LEWT::Extension.new.lewt_extensions
|
48
|
+
|
49
|
+
|
50
|
+
# Load core extensions
|
51
|
+
load_extensions( File.expand_path('../extensions', __FILE__) )
|
52
|
+
|
53
|
+
if core_settings.has_key?("lewt_stash")
|
54
|
+
# load user defined extesnions
|
55
|
+
load_extensions
|
56
|
+
end
|
57
|
+
|
58
|
+
# Stores default options returned from extensions
|
59
|
+
# and the LEWT defaults at large
|
60
|
+
options = {}
|
61
|
+
@command = ARGV[0]
|
62
|
+
|
63
|
+
# argument supplied for `cmd' (if any). ignore option flags IE args that start with the `-' symbol
|
64
|
+
@argument = ARGV[1] == nil || ARGV[1].match(/\-/i) ? nil : ARGV[1]
|
65
|
+
|
66
|
+
@options = LewtOpts.new( @extensions, library_options )
|
67
|
+
|
68
|
+
parse_internal_commands
|
69
|
+
end
|
70
|
+
|
71
|
+
# Runs extract, process, render hooks.
|
72
|
+
def run
|
73
|
+
extract = fire_hooks("extract", @options)
|
74
|
+
process = fire_hooks("process", @options, extract )
|
75
|
+
render = fire_hooks("render", @options, process )
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# fn returns the desired customer given there name or alias.
|
80
|
+
# A REGEXP query can be passed to this object i.e. "ACME|NovaCorp|..."
|
81
|
+
# to match multiple customers.
|
82
|
+
# query [String]:: The query to search against.
|
83
|
+
def get_client( query )
|
84
|
+
client = nil
|
85
|
+
@customers.each do |c|
|
86
|
+
buildQ = [ c["name"], c["alias"] ].join("|")
|
87
|
+
regex = Regexp.new(buildQ, Regexp::IGNORECASE)
|
88
|
+
if regex.match( query ) != nil
|
89
|
+
client = c
|
90
|
+
end
|
91
|
+
end
|
92
|
+
return client
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# Fire the logic loop from the specified hook onwards. Used when piping data in from CL
|
97
|
+
# hook [Sting]:: extract, process, or render
|
98
|
+
# data:: The data to pass to fire_hooks
|
99
|
+
def hook_process (hook, data)
|
100
|
+
case hook
|
101
|
+
when "extract"
|
102
|
+
extracted_data = fire_hooks("extract",@options,data)
|
103
|
+
hook_process("process", extracted_data)
|
104
|
+
when "process"
|
105
|
+
processed_data = fire_hooks("process",@options,data)
|
106
|
+
hook_process("render",processed_data)
|
107
|
+
when "render"
|
108
|
+
render_data = fire_hooks("render",@options,data)
|
109
|
+
else
|
110
|
+
raise ArugmentError, "#{self.class.name}.hook_process requires the start hook to be either render or process"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Fire a hook with the given options and overloaded parameters.
|
115
|
+
# hook [String]:: Expected hooks are 'extract', 'process', 'render'.
|
116
|
+
# options [Hash]:: The options gathered from using LewtOpts
|
117
|
+
# data [Mixed]:: The data to pass to the extensions.
|
118
|
+
def fire_hooks( hook, options, *data )
|
119
|
+
algamation = Array.new
|
120
|
+
@extensions.each { |e|
|
121
|
+
## filter hooks
|
122
|
+
filter_regex = /#{options[hook.to_sym].gsub(",","|")}/
|
123
|
+
if e.methods.include?(hook.to_sym) and e.command_name.match(filter_regex)
|
124
|
+
case hook
|
125
|
+
when "extract"
|
126
|
+
algamation.concat e.extract(options)
|
127
|
+
when "process"
|
128
|
+
processed = e.process(options, *data)
|
129
|
+
if processed.kind_of? Array
|
130
|
+
algamation.concat processed
|
131
|
+
else
|
132
|
+
algamation.push processed
|
133
|
+
end
|
134
|
+
when "render"
|
135
|
+
algamation.concat e.render(options, *data)
|
136
|
+
# dump render output to console of option specified.
|
137
|
+
puts algamation if options[:dump_output] == true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
}
|
141
|
+
return algamation;
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
protected
|
146
|
+
|
147
|
+
# reads input from standard INPUT
|
148
|
+
def read_stdin
|
149
|
+
data = ""
|
150
|
+
while line = $stdin.gets
|
151
|
+
data += line
|
152
|
+
end
|
153
|
+
return data
|
154
|
+
end
|
155
|
+
|
156
|
+
# Parses lewsts intenral commands. If none have been invoked it simple returns.
|
157
|
+
def parse_internal_commands
|
158
|
+
return if @command == nil
|
159
|
+
# raise argument error if command invoked without argument
|
160
|
+
if @argument == nil and @command != nil
|
161
|
+
puts @command
|
162
|
+
raise ArgumentError, "Class #{self.class.name} requires an argument t be supplied with a command"
|
163
|
+
end
|
164
|
+
|
165
|
+
if @command.match(/pipe/)
|
166
|
+
# pipe stdin into fire_hook(@argument) event
|
167
|
+
data = Psych.load(read_stdin)
|
168
|
+
if data != nil
|
169
|
+
hook_process(@argument, data)
|
170
|
+
else
|
171
|
+
raise ArgumentError, "could not parse STDIN pipe as YAML data."
|
172
|
+
end
|
173
|
+
exit
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
# Loads all installed LEWT extensions by checking the ext_dir setting variable for available ruby files.
|
179
|
+
# directory [String]:: The path where to look for extensions as a string.
|
180
|
+
def load_extensions( directory = @lewt_stash + "extensions" )
|
181
|
+
raise Exception, "Directory does not exist: #{directory}" if !Dir.exists?(directory)
|
182
|
+
|
183
|
+
Dir.foreach( directory ) do |file|
|
184
|
+
next if (file =~ /\./) == 0
|
185
|
+
# Cannot match with File.directory?(file) due to how ruby performs
|
186
|
+
# this test internaly when script is called from another dir.
|
187
|
+
# Therefor some REGEX will be used to match the .rb file extension instead...
|
188
|
+
if file.match(/\.rb/) != nil
|
189
|
+
load(directory + "/" + file)
|
190
|
+
ext_object = initialize_extension( file )
|
191
|
+
else
|
192
|
+
# is a directory
|
193
|
+
# load file in dir named {dir_name}.rb
|
194
|
+
load "#{directory}/#{file}/#{file}.rb"
|
195
|
+
ext_object = initialize_extension(file)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# load extensions packaged as GEMS
|
200
|
+
if @settings['gem_loads'] != nil
|
201
|
+
@settings['gem_loads'].split(Lewt::OPTION_DELIMITER_REGEX).each do |g|
|
202
|
+
match = g.split("/")
|
203
|
+
ext_object = initialize_gem(match[0], match[1])
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
# This method initialised a gem as specifed in your settings file.
|
210
|
+
# gem_require [String]:: The gem require path as a string
|
211
|
+
# gem_class [String]:: The gem class name for initialisation
|
212
|
+
def initialize_gem( gem_require, gem_class )
|
213
|
+
require gem_require
|
214
|
+
extension = eval( gem_class + ".new")
|
215
|
+
return @extensions.last
|
216
|
+
end
|
217
|
+
|
218
|
+
# fn basically anticipates a class name given its file path and then call its registerHanders method.
|
219
|
+
# Class names are transformed into UC words. '-' are interpreted as spaces in this conversion, and are
|
220
|
+
# striped afterwards to return something like 'invoice-renderer.rb' >>> 'InvoiceRenderer' ready for
|
221
|
+
# evaluation.
|
222
|
+
# file [String]:: The file name as a string
|
223
|
+
def initialize_extension ( file )
|
224
|
+
classConvention = file.gsub("-", " ").split.map(&:capitalize).join(' ').gsub(" ","").gsub(".rb","")
|
225
|
+
extInit = ("LEWT::" + classConvention + ".new").to_s
|
226
|
+
extension = eval( extInit )
|
227
|
+
# extension will be registered on init so just reference the last one
|
228
|
+
return @extensions.last
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
data/lib/lewt_book.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Author:: Jason Wijegooneratne (mailto:code@jwije.com)
|
2
|
+
# Copyright:: Copyright (c) 2014 Jason Wijegooneratne
|
3
|
+
# License:: MIT. See LICENSE.md distributed with the source code for more information.
|
4
|
+
|
5
|
+
module LEWT
|
6
|
+
|
7
|
+
|
8
|
+
# The LEWTBook is used by extractors as a generic data structure to adhere to when
|
9
|
+
# returning there results.
|
10
|
+
# It follows a basic *general ledger* format without the double-entry book keeping.
|
11
|
+
#
|
12
|
+
# ===Usage:
|
13
|
+
#
|
14
|
+
# lewtbook = LEWTBook.new()
|
15
|
+
# lewtbook.add_row( LEWTLedger.new( params, ... ) )
|
16
|
+
#
|
17
|
+
class LEWTBook < Array
|
18
|
+
# adds a row to this element as per array.push. only accespts LEWTLedger objects as entries.
|
19
|
+
# element [LEWTLedger]:: An initialised LEWTLedger object containing your data.
|
20
|
+
def push ( element )
|
21
|
+
if element.kind_of?(LEWT::LEWTLedger)
|
22
|
+
self.class.superclass.instance_method(:push).bind(self).call element
|
23
|
+
else
|
24
|
+
raise TypeError, "Class #{self.class.name} only accepts LEWT::LEWTLedger objects as its contents"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/lewt_ledger.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module LEWT
|
4
|
+
|
5
|
+
# LEWTLedger is a pre-formated hash structure that conforms somewhat to a standard general ledger entry.
|
6
|
+
#
|
7
|
+
# ===Keys
|
8
|
+
# date_start:: Start date the entry occured on
|
9
|
+
# date_end:: End date the entry occured on
|
10
|
+
# category:: Some sort of general category for this entry i.e: 'Hourly Income', 'Operating Expenses' etc.
|
11
|
+
# entity:: The entiry with whom this transaction occured with
|
12
|
+
# description:: A description of the entry
|
13
|
+
# quantity:: How many units
|
14
|
+
# unit_cost:: The cost per unit
|
15
|
+
# sub_total (optional):: A total or defaults to quantity * unit_cost
|
16
|
+
# gst (optional):: The GST (VAT) amount to be added for this entry. Defaults to 0.
|
17
|
+
# total (optional):: The total, including tax, for this entry. Defaults to sub_total + gst
|
18
|
+
#
|
19
|
+
# ===Usage
|
20
|
+
# ledger = LEWTLedger.new(params, ...)
|
21
|
+
#
|
22
|
+
# Furthermore LEWTLedger provides some methods to work with metatags. Metatags can be embedded inside your extraction sources.
|
23
|
+
# I like to put mine in my 'description' fields, it works well with Calender Extractor. The meta data is basically a hash tag, you
|
24
|
+
# can do stuff like this:
|
25
|
+
#
|
26
|
+
# '#happiness=10/10'
|
27
|
+
# '#ignore-cost'
|
28
|
+
#
|
29
|
+
# meta tags that do not assign a value [ie: =something] will evaluate to a boolean true flag. If you assign a value it must
|
30
|
+
# be a fraction, this will be evaluated as a ruby Rational type. Your extensions can respond to these tags however they like.
|
31
|
+
# The metatags can be accessed with the <tt>metatags</tt> reader attribute:
|
32
|
+
# ledger_data.metatags
|
33
|
+
#
|
34
|
+
|
35
|
+
class LEWTLedger < Hash
|
36
|
+
|
37
|
+
attr_reader :metatags
|
38
|
+
|
39
|
+
# This is a general matching regex for metatags.
|
40
|
+
MATCH_SINGLE_META_REGEX = /[#](\S*)/
|
41
|
+
MATCH_MULTIPLE_META_REGEX = /#\S*/
|
42
|
+
|
43
|
+
def initialize (args)
|
44
|
+
raise ArgumentError, "#{self.class.name} was not instantized with valid parameters" if valid?(args) == false
|
45
|
+
self[:date_start] = args[:date_start]
|
46
|
+
self[:date_end] = args[:date_end]
|
47
|
+
self[:category] = args[:category]
|
48
|
+
self[:entity] = args[:entity]
|
49
|
+
self[:description] = args[:description]
|
50
|
+
self[:quantity] = args[:quantity]
|
51
|
+
self[:unit_cost] = args[:unit_cost]
|
52
|
+
self[:sub_total] = self[:sub_total] || self[:quantity] * self[:unit_cost]
|
53
|
+
self[:gst] = args[:gst] || 0
|
54
|
+
self[:total] = args[:total] || ( self[:sub_total] + self[:gst] )
|
55
|
+
@metatags = parse_meta_tags(:description)
|
56
|
+
strip_readable_meta if @metatags != nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# parses a field on this object for meta data. Meta data can be embedded inside the ledger fields
|
60
|
+
# as a string by prefixing it with the '#' symbol. If you assign a value to the meta field with the '='
|
61
|
+
# symbol, its value will be interpreted as a number.
|
62
|
+
# ie:
|
63
|
+
# #good-pay // true
|
64
|
+
# #happiness=6/10 // Rational(6,10)
|
65
|
+
# field_key [Symbol]:: the ledger key you wish to parse as a symbol.
|
66
|
+
def parse_meta_tags ( field_key )
|
67
|
+
value = self[field_key]
|
68
|
+
tags = parse_tags value
|
69
|
+
return tags
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
# strips the meta data from a field so that it is no longer readable. leaves the metatags property on the object intact however
|
75
|
+
# field:: A Symbol corresponding to the field on this object to strip. Defaults to the description field.
|
76
|
+
def strip_readable_meta ( field = :description )
|
77
|
+
self[field].scan(LEWTLedger::MATCH_MULTIPLE_META_REGEX).each do |m|
|
78
|
+
self[field].slice!(m).strip!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# this function extracts all tags/values from a given string.
|
83
|
+
# string [String]:: a string to search for meta tags in.
|
84
|
+
def parse_tags (string)
|
85
|
+
tags = nil
|
86
|
+
string.scan(LEWTLedger::MATCH_SINGLE_META_REGEX) { |t|
|
87
|
+
if tags == nil
|
88
|
+
tags = Hash.new
|
89
|
+
end
|
90
|
+
tag_value = parse_tag_value t[0]
|
91
|
+
tag_name = parse_tag_name t[0]
|
92
|
+
tags[tag_name.gsub(/\W/,"_").to_sym] = tag_value
|
93
|
+
}
|
94
|
+
return tags
|
95
|
+
end
|
96
|
+
|
97
|
+
# parses the name of a tag and returns it as a symbol to be used as a hash key
|
98
|
+
# tag [string] a string containing the a singular meta tag.
|
99
|
+
def parse_tag_name( tag )
|
100
|
+
match_name = /[^=]*/
|
101
|
+
m = tag.match(match_name)
|
102
|
+
return m[0]
|
103
|
+
end
|
104
|
+
|
105
|
+
# parses the value of a meta tag string. if just the string is given (ie: no = xx/xx) then the tag will have
|
106
|
+
# a value of true returned for it.
|
107
|
+
# tag [String]:: a meta tag string
|
108
|
+
def parse_tag_value ( tag )
|
109
|
+
match_value = /[=]\d*\/\d*/
|
110
|
+
match = tag.match match_value
|
111
|
+
# if no value found then this must be a boolean switch (because a tag was parsed from the field!) so set value to true
|
112
|
+
value = match != nil ? extract_fraction(match[0]) : true
|
113
|
+
return value
|
114
|
+
end
|
115
|
+
|
116
|
+
# extracts a mathematical expression from a single tag
|
117
|
+
# format: Num +-/* Num
|
118
|
+
# string:: the string to extract the fraction from
|
119
|
+
def extract_fraction ( string )
|
120
|
+
match_fraction = /(\d{1,})([\/\+])(\d{1,})+/
|
121
|
+
# m = string.match(match_fraction)[0]
|
122
|
+
m = string.match(match_fraction)
|
123
|
+
value = nil
|
124
|
+
if m != nil
|
125
|
+
value = Rational m[0]
|
126
|
+
end
|
127
|
+
return value
|
128
|
+
end
|
129
|
+
|
130
|
+
# validates the arguments this object is initialised with.
|
131
|
+
# args [Hash]:: The argument hash passed to this class on initialize
|
132
|
+
def valid?(args)
|
133
|
+
raise TypeError, "#{self.class.name} must be initialised with a hash" if not args.kind_of?(Hash)
|
134
|
+
args.each { |k,v|
|
135
|
+
case k
|
136
|
+
when :date_start, :date_end
|
137
|
+
raise TypeError, "Expected Time type" unless v.kind_of? Time
|
138
|
+
when :category, :entity, :description
|
139
|
+
raise TypeError, "Expected a string" unless v.kind_of? String
|
140
|
+
when :quantity, :unit_cost, :sub_total, :gst, :total
|
141
|
+
raise TypeError, "Expected a number" unless v.kind_of? Numeric
|
142
|
+
end
|
143
|
+
}
|
144
|
+
return true
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
data/lib/lewtopts.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# Author:: Jason Wijegooneratne (mailto:code@jwije.com)
|
2
|
+
# Copyright:: Copyright (c) 2014 Jason Wijegooneratne
|
3
|
+
# License:: MIT. See LICENSE.md distributed with the source code for more information.
|
4
|
+
|
5
|
+
|
6
|
+
module LEWT
|
7
|
+
|
8
|
+
# This is the options handling class for LEWT extensions. It handles translating extension options for usage in the command line
|
9
|
+
# & library run-times and acts as a sort of wrapper class for this functionality.
|
10
|
+
#
|
11
|
+
# ===LEWT's reserved option flags
|
12
|
+
#
|
13
|
+
# -x --extract:: what extractor[s] to use
|
14
|
+
# -p --process:: what processor[s] to use
|
15
|
+
# -o --render:: what renderer[s] to use
|
16
|
+
# -t --target:: target
|
17
|
+
# -s --start:: start target date
|
18
|
+
# -e --end:: end target date
|
19
|
+
#
|
20
|
+
# The user defined values for these options are readable by extensions at runtime.
|
21
|
+
|
22
|
+
|
23
|
+
class LewtOpts < Hash
|
24
|
+
|
25
|
+
attr_reader :options, :defaults
|
26
|
+
|
27
|
+
# Sets up this extension.
|
28
|
+
def initialize ( extensions, library_options = nil )
|
29
|
+
default_options = {
|
30
|
+
:start => {
|
31
|
+
:definition => "Start time for LEWT snapshot",
|
32
|
+
:default => DateTime.now - 8,
|
33
|
+
:type => DateTime,
|
34
|
+
:short_flag => "-s"
|
35
|
+
},
|
36
|
+
:end => {
|
37
|
+
:definition => "End time for LEWT snapshot",
|
38
|
+
:default => DateTime.now,
|
39
|
+
:type => DateTime,
|
40
|
+
:short_flag => "-e"
|
41
|
+
},
|
42
|
+
:target => {
|
43
|
+
:definition => "The target to filter data with. In the case of most extensions this will be the target customers but other alternatives are possible",
|
44
|
+
:short_flag => "-t",
|
45
|
+
:type => String
|
46
|
+
},
|
47
|
+
:extract => {
|
48
|
+
:definition => "The extraction extension(s) LEWT should use to pull data with. This can be a comma separated list for multiple sources",
|
49
|
+
:default => "calendar",
|
50
|
+
:type => String,
|
51
|
+
:short_flag => "-x"
|
52
|
+
},
|
53
|
+
:process => {
|
54
|
+
:definition => "The processor extensions LEWT should use to process the data with.",
|
55
|
+
:default => "invoice",
|
56
|
+
:type => String,
|
57
|
+
:short_flag => "-p"
|
58
|
+
},
|
59
|
+
:render => {
|
60
|
+
:definition => "The render(s) LEWT should use to output the data with. This can be a comma separate list for multiple outputs.",
|
61
|
+
:default => "liquid_render",
|
62
|
+
:type => String,
|
63
|
+
:short_flag => "-o"
|
64
|
+
},
|
65
|
+
:dump_output => {
|
66
|
+
:definition => "Toggle dumping output to console or log",
|
67
|
+
:default => true,
|
68
|
+
:short_flag => "-d"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
# gather extension options & merge into LEWTs defaults
|
73
|
+
extensions.each do |e|
|
74
|
+
if e.options != nil
|
75
|
+
default_options.merge!( e.options )
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
@defaults = default_options
|
80
|
+
|
81
|
+
# determine if using LEWT from command line (CL) or as a drop in library and parse options accordingly
|
82
|
+
if File.basename($0).match(/\.rb/) != nil
|
83
|
+
parse_library_options( default_options, library_options )
|
84
|
+
else
|
85
|
+
parse_command_line_options( default_options )
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
# handles parsing & translating options in command line mode
|
91
|
+
# sets the value of the instantized object equal to the parsed options hash
|
92
|
+
# default_options [Hash]:: The default options gathered from all extension & Lewt itself to use in case user supplied values aren't given.
|
93
|
+
def parse_command_line_options( default_options )
|
94
|
+
options = self
|
95
|
+
|
96
|
+
# Parse internal commands before extension commands & options to avoid any conflicts & to avoid extension invocation
|
97
|
+
# in case a internal command is called.
|
98
|
+
OptionParser.new do |opts|
|
99
|
+
|
100
|
+
default_options.each do | name, details |
|
101
|
+
# translate options default value if defined
|
102
|
+
if details.key?(:default) == true
|
103
|
+
options[name] = details[:default]
|
104
|
+
end
|
105
|
+
|
106
|
+
cl_type = details[:type].to_s == "DateTime" ? String : details[:type]
|
107
|
+
cl_sub = details[:type].to_s == "DateTime" ? "Date" : details[:type]
|
108
|
+
|
109
|
+
cl_option = "--#{name.to_s.gsub("_","-")} [#{cl_sub}]"
|
110
|
+
|
111
|
+
if details.key?(:short_flag) == true && details.key?(:type) == true
|
112
|
+
opts.on( details[:short_flag], cl_option, cl_type, details[:definition] ) do |o|
|
113
|
+
options[ name ] = prepare_input( details, o )
|
114
|
+
end
|
115
|
+
elsif details.key?(:short_flag) == true && details.key?(:type) == false
|
116
|
+
opts.on( details[:short_flag], "--#{name.to_s.gsub("_","-")}", details[:definition] ) do |o|
|
117
|
+
options[ name ] = prepare_input( details, o )
|
118
|
+
end
|
119
|
+
elsif details.key?(:short_flag) == false
|
120
|
+
opts.on( cl_option, cl_type, details[:definition] ) do |o|
|
121
|
+
options[ name ] = prepare_input( details, o )
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
opts.banner = "Usage: lewt -x EXTRACTOR -p PROCESSOR -o RENDERER"
|
128
|
+
opts.version = LEWT::VERSION
|
129
|
+
|
130
|
+
end.parse!(ARGV)
|
131
|
+
|
132
|
+
return options
|
133
|
+
end
|
134
|
+
|
135
|
+
# handles parsing & translating options in library mode
|
136
|
+
# sets the value of the instantized object equal to the parsed options hash
|
137
|
+
# default_options [Hash]:: The default options gathered from all extension & Lewt itself to use in case user supplied values aren't given.
|
138
|
+
# library_options [Hash]:: Some options translated for use in lib mode
|
139
|
+
def parse_library_options ( default_options, library_options )
|
140
|
+
options = Hash.new
|
141
|
+
|
142
|
+
default_options.each do | name, details |
|
143
|
+
# translate options default value if defined
|
144
|
+
if details.key?(:default) == true
|
145
|
+
options[name] = details[:default]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# merge library options with options array
|
150
|
+
options.merge!(library_options)
|
151
|
+
|
152
|
+
# assign options to self
|
153
|
+
options.each do |k,v|
|
154
|
+
self[k] = v
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# this function basically exists to get around the limited variable initialization functionality of
|
159
|
+
# Ruby standard option parser class.
|
160
|
+
def prepare_input ( details, value )
|
161
|
+
type = details[:type]
|
162
|
+
if type == DateTime
|
163
|
+
initialized_value = DateTime.parse(value)
|
164
|
+
else
|
165
|
+
initialized_value = value
|
166
|
+
end
|
167
|
+
return initialized_value
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|