-
Notifications
You must be signed in to change notification settings - Fork 0
3 Creating Extensions for LEWT
This is a quick primer on how to get started creating extensions for LEWT.
The LEWT extension system is pretty basic, your extension will generally fall into one (or a few) of the following categories.
- extractor: extracts data from sources (e.g. accounting data inside a spreadsheet file) and pre-process it into the LEWTBook format for further processing.
- processor: processes the data generated by the extraction phase into useful data (such as generating an invoice).
- renderer: renders the processed data for human reading OR for further use in other programs etc.
To get started create a ruby file in your extensions [default: /path/to/lewt/lib/extensions] folder called myextension.rb where myextension is the name you would like to give your actual class i.e Myextension
, this naming convention is important for initialisation.
If you want to camel case your class name your file with '-' to separate the words i.e. my-extension.rb
would be invoked as MyExtension
. If you have a few files you want to include create a directory in your extensions folder that observes the above naming conventions and put your files in there e.g.:
| my-extension/
|---- my-extension.rb
|---- other_methods.rb
Back to the code! Inside my-extension.rb type:
module LEWT
# My class documentations marked up with rDoc
MyExtension < LEWT::Extension
end
end
All extension must declare themselves within the LEWT module, this is to avoid name spacing issues with other packages.
Next we need to register the extension within LEWT on initialisation.
module LEWT
MyExtension < LEWT::Extension
def initialize
options = {
:option_name {
:definition => "Give your option a nice definition, this will be viewable when users use lest --help",
:type => String, # specify its type
:default => "value" # you don't have to provide defaults
}
super( :cmd => "my_command_name", :options => options })
end
end
The initilize method now registers our extension within LEWT! It does this by invoking the LEWT::Extension
base class' super
method. The super
method requires your pass it a Hash containing the following keys
-
:cmd
- The command name your extension should respond to when lewt is invoked -
:options
- Any runtime options you would like to register for your extension
On Options
Your extension can register some options within the LEWT system as per the code example above. The options hash is a hash of hashes. Each key in it specifies the option flag i.e. :my_options => {}
above translates to lewt --my-option
when using LEWT from the command line or ```options = { :my_option => ... } when using LEWT as a library.
Finally your extension must implement at least one of the the three available extension callback methods.
-
extract ( options )
: use this if you want your extension to return extracted data. -
process ( options, data )
: use this method if you want your extension to process extracted data. -
render ( options, data )
: use this method if you want your extension to format processed data for consumption.
More on these below.
Extractors must implement the extract
method:
# ...
# code omitted
# options [Hash]:: The options hash passed to this function by the Lewt program at run-time.
def extract ( options )
end
This method will be passed an options hash containing the options passed to LEWT at run-time. For example :my_option
from above could be accessed as options[:my_option]
within the extract method.
Extractors must return there data in the LEWTBook format. LEWTBook is an array like object that contains instances of the LEWTLedger object. The LEWTLedger class is a basic data structure for storing accounting data - or more simply its a data structure that represents a general ledger within the LEWT program. Your extract method can use these as follow:
def extract (options)
books = LEWT::LEWTBook.new
if options[:my_option] == true
# extract our transaction data from some source
extracted_data = self.run_my_extract_process
# iterate it and create a LEWTLedger row for each transaction
extracted_data.each do |row|
ledger_row = LEWT::LEWTLedger.new( {
:start => row.date_start,
:end => row.date_end,
:entity => row.customer_name,
:description => row.transaction_notes,
:quantity => row.hours_count,
:unit_cost => row.hourly_rate,
# :total => specifing a total is optional. The LEWTLEdger will calculate this on init for you.
end
books.push(ledger_row)
else
raise ArgumentError, "You must specify :my_option for this extension!"
end
return books
end
It is important that your extension return its extracted data in the LEWTBook format, otherwise processors won't understand the data you are passing to them.
Processors process extracted data into something useful like an invoice or a report. They receive the LEWT options hash (same as with extract method) and the data in LEWTBook format. The LEWTBook is an array like object so it can be iterated to process the transaction data. Here is an example implementation of the process
callback. It simply totals up expenses and income from the LEWTBook passed to it and returns the data as a hash usable by the renderer.
# options [Hash]:: The options hash passed to this function by the Lewt program at run-time.
# data [LEWTBook]:: The data in LEWTBook format
def process ( options, data )
totals = {
:income => 0,
:expenses => 0
}
data.each do |row|
if row.category.match Expenses
totals[:expenses] += row.total
elsif row.category.match Income
totals[:income] += row.total
end
end
return totals
end
It's pretty simple really. Your processor can do any number of things, including hooking into the Metatag feature of the LEWTLedger class. All you need to do is return a hash with your processed data in it.
The render callback receives the LEWT options hash as with the other callbacks, as well as the processed data in the form of an array of hashes. Below is an example render implementation that converts the processed data to YAML and outputs it to the terminal.
# options [Hash]:: The options hash passed to this function by the Lewt program.
# data [Array]:: The processed data as an array of hashes
def render ( options, data )
data.each do |d|
puts d.to_yaml
end
end
A render could also be used to render data to a remote server or something. The idea behind them is to get the data in the format you want, where you want it.
No worries. Package your extension however you like, install its .gem file, then just remember to add the following key to your settings.yml file:
gem_loads: require_path/MODULE::Class,
So for example, given MyExtension from above we would load this as:
gem_loads: myextension/LEWT::MyExtension
require_path is simple the argument supplied to ruby's require
statement. MODULE::Class
will be invoked as MODULE::Class.new
to initialise your extension.
And that's pretty much it really!