Skip to content

3 Creating Extensions for LEWT

jdwije edited this page Sep 17, 2014 · 8 revisions

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

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

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.

Renderers

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.

Packaging your extensions as GEMS

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!