conjuror
Version:
A magical CSV data parsing and outputing wizard or witch
384 lines (308 loc) • 12.8 kB
JavaScript
var chalk = require('chalk')
var cheerio = require("cheerio")
var fs = require("fs")
var _ = require('underscore')
var moment = require('moment')
var net = require('net')
var path = require('path')
var repl = require('repl')
var csv = require('csv')
var wkhtmltopdf = require('wkhtmltopdf')
var read = require('datapackage-read')
// CLI UI
var line = "---------------------------------------------------------------------"
// File Manipulation
var SaveFile = require('./save_file')
// Conjuror
var Conjuror = require('./conjuror.prepareRecipe.js')
Conjuror.summonUser = function(callback){
// TODO: Should probably remove the depency on args here & pass as a variable
if (Conjuror.args.config !== undefined && Conjuror.args.config.user !== undefined) {
return callback(Conjuror.args.config.user)
} else {
return callback({'exists': false})
}
}
Conjuror.magickData = function(data, resource, date) {
var outputs = {
totals: {
hours: 0,
money: 0.00,
tax: 0.00,
total: 0.00
},
csv: _.pluck(resource.schema.fields, 'name').join(','),
cli: '',
html: ''
}
// Filter by Date / Type
var date_filter = 'none'
if (date !== undefined && date !== "") {
// TODO: would it be easier to just use moment.js for this?
var is_date_full = /[0-9]{4}-[0-9]{2}-[0-9]{2}/
var is_date_year_month = /[0-9]{4}-[0-9]{2}/
var is_date_month_day = /[0-9]{2}-[0-9]{2}/
var is_date_year = /[0-9]{4}/
var is_week = /week/
var is_month = /month/
var is_today = /today/
if (date.indexOf('before:') > -1) {
date_filter = 'before'
} else if (date.indexOf(':to:') > -1) {
date_filter = 'range'
} else if (is_week.exec(date)) {
date_filter = 'this_week'
} else if (is_today.exec(date)) {
date_filter = 'today'
} else if (is_month.exec(date)) {
date_filter = 'this_month'
} else if (is_date_full.exec(date)) {
date_filter = 'full'
} else if (is_date_year_month.exec(date)) {
date_filter = 'year_month'
} else if (is_date_month_day.exec(date)) {
date_filter = 'month_day'
} else if (is_date_year.exec(date)) {
date_filter = 'year'
} else {
date_filter = 'month'
}
console.log(chalk.blue(' - Date filter: ' + date_filter + ': ' + date))
}
// Make Totals
var increment_output = function(item) {
// For recipe "process" instructions #
outputs.totals.hours += item.time
outputs.totals.money += (item.time * item.rate)
// CLI format
var cli_print_basics = item.date + '\t ' + item.time + ' \t' + item.client + ' \t'
var cli_print_details = item.description
var cli_print_length = cli_print_basics.length + cli_print_details.length
var cli_print_output = cli_print_basics + cli_print_details
// Trim to long
if (cli_print_output.length > 65) {
cli_print_output = cli_print_output.substring(0, 60) + '...'
}
outputs.cli += cli_print_output + '\n'
// HTML format
if (_.indexOf(Conjuror.args.options.formats, 'html') > -1 || _.indexOf(Conjuror.args.options.formats, 'pdf') > -1) {
outputs.html += '\n<tr>\n'
outputs.html += ' <td class="width-50">' + item.date + '</td>\n'
outputs.html += ' <td class="width-50">' + item.client + '</td>\n'
outputs.html += ' <td class="text-right">' + item.time + '</td>\n'
outputs.html += ' <td class="text-left"> hrs</td>\n'
outputs.html += ' <td>' + item.description + '</td>\n'
outputs.html += '</tr>'
}
}
_.each(data, function(line, index) {
// skip the first & empty lines
if (index !== 0 && line != "") {
var parts = line
// Filter Date & Trim
var check_date = Conjuror.Date[date_filter](parts[0], date)
var check_trim = Conjuror.Trim(Conjuror.args.options.trim, parts)
var check_search = Conjuror.Search(parts, Conjuror.args.options.search)
// Does Item Meet Filter (date, trim)
if (_.indexOf([check_date, check_trim, check_search], false) === -1) {
// Build CSV format (FIXME: nasty code organization going on here)
if (_.indexOf(Conjuror.args.options.format, 'csv') > -1) {
outputs.csv += '\n' + line.join(',')
}
var item_output = Conjuror.murmurLineToSchema(line, resource.schema)
increment_output(item_output)
}
}
})
// Outputs - hook into from recipe instructions #43
outputs.totals.hours = outputs.totals.hours.toFixed(2)
outputs.totals.money = outputs.totals.money.toFixed(2)
if (resource.payment.tax_percent > 0.0) {
outputs.totals.tax = (resource.payment.tax_percent * outputs.totals.money).toFixed(2)
outputs.totals.total = (parseFloat(outputs.totals.tax) + parseFloat(outputs.totals.money)).toFixed(2)
} else {
outputs.totals.tax = "0.0"
outputs.totals.total = outputs.totals.money
}
return outputs
}
Conjuror.castToCSV = function(outputs) {
if (_.indexOf(Conjuror.args.options.formats, 'csv') > -1) {
// File Name
var output_name = 'Conjuror Output - ' + moment().formats('D MMMM YYYY')
if (Conjuror.args.options.output) {
output_name = Conjuror.args.options.output
}
// Save CSV
var saveFile = SaveFile(fs, Conjuror.args.app_path + 'output/' + output_name + '.csv', outputs.csv)
}
}
// Output HTML
Conjuror.castToHTML = function(outputs, user, resource) {
if (_.indexOf(Conjuror.args.options.formats, 'html') > -1 || _.indexOf(Conjuror.args.options.formats, 'pdf') > -1) {
// Determine Template & Path
var template = Conjuror.args.config.default_template || 'invoice'
if (resource.templates !== undefined && resource.templates.length > 0) {
template = resource.templates[0]
}
var template_path = Conjuror.args.app_path + 'templates/'
if (Conjuror.args.config.path_templates !== undefined &&
Conjuror.args.config.path_templates != "") {
template_path = Conjuror.args.config.path_templates
}
// Load Template
Conjuror.readManuscript(template_path + template + '.html')
.then(function(buffer) {
console.log(chalk.green(chalk.blue(" - Loaded template: " + template_path + template + '.html')))
var output_name = 'Invoice - ' + moment().format('D MMMM YYYY')
// Name
if (outputs.client) {
output_name = Conjuror.args.options.invoicenumber + ' - ' +
outputs.client.name + ' - ' + moment().format('DD.MM.YYYY')
}
if (Conjuror.args.options.output) {
output_name = Conjuror.args.options.output
}
if (Conjuror.args.options.generated && Conjuror.args.options.generated !== 'today') {
var generated_date = Conjuror.args.options.generated
} else {
var generated_date = moment().format('Do MMMM, YYYY')
}
// TODO: hook into recipes #43
if (Conjuror.args.options.details) {
var data_details = Conjuror.args.options.details
} else {
var data_details = 'show'
}
if (Conjuror.args.options.message) {
var data_message = Conjuror.args.options.message
} else {
var data_message = false
}
// HTML Template
var template_file = buffer.toString("utf8", 0, buffer.length)
var template_html = _.template(template_file)
var template_data = {
resource: resource,
invoice_number: Conjuror.args.options.invoicenumber,
generated_name: output_name,
generated_date: generated_date,
data_details: data_details,
data_message: data_message,
hours_rows: outputs.html,
hours_total: outputs.totals.hours,
money_total: outputs.totals.money,
tax_total: outputs.totals.tax,
total_total: outputs.totals.total
}
// Currency
if (user !== undefined) {
template_data.user = user
template_data.currency = user.currency
}
template_data.currency = Conjuror.args.options.currency || 'USD'
template_data.extra = Conjuror.args.options.extra || ''
// Output HTML & Path
var output_html = template_html(template_data)
var output_path = Conjuror.args.app_path + 'output/'
if (Conjuror.args.config.path_output !== undefined &&
Conjuror.args.config.path_output != "") {
output_path = Conjuror.args.config.path_output
}
// Save HTML file
if (_.indexOf(Conjuror.args.options.formats, 'html') > -1) {
SaveFile(fs, output_path + output_name + '.html', output_html)
}
// Save PDF file
if (_.indexOf(Conjuror.args.options.formats, 'pdf') > -1) {
Conjuror.castHTMLToPDF(output_path, output_html, output_name)
}
}, function(err) {
console.log(chalk.red("Failed to find template: " + template_path + template + '.html'))
})
}
}
Conjuror.castHTMLToPDF = function(output_path, output_html, output_name) {
if (_.indexOf(Conjuror.args.options.formats, 'pdf') > -1) {
console.log(chalk.blue(' - Saving as PDF: ' + output_path + output_name + '.pdf'))
console.log(chalk.gray(line))
wkhtmltopdf(output_html, {
pageSize: 'letter',
output: output_path + output_name + '.pdf'
})
} else {
console.log(chalk.red('Somehow exporting to PDF failed'))
}
}
// Load Data & Parse
Conjuror.Twirl = function(path, resource, callback) {
var resource_file = path + '/' + resource.path
Conjuror.readManuscript(resource_file)
.then(function(buffer) {
var data = buffer.toString("utf8", 0, buffer.length)
// Loop Through Items
csv.parse(data, function(err, data) {
if (err) console.log(err)
var outputs = Conjuror.magickData(data, resource, Conjuror.args.options.date)
// Output CSV - rough implementation https://github.com/bnvk/Conjuror/issues/36
Conjuror.castToCSV(outputs)
var cli_hours = outputs.totals.hours + ' hours'
// Overwrite "outputs.totals" when there is a fixed price
if ((Conjuror.args.options.price) && (Conjuror.args.options.price != 'tally')) {
outputs.totals.money = Conjuror.args.options.price
outputs.totals.tax = (Conjuror.args.options.price * resource.payment.tax_percent)
outputs.totals.total = (parseFloat(Conjuror.args.options.price) + parseFloat(outputs.totals.tax))
}
var cli_currency = (resource.payment.currency || Conjuror.args.options.currency || 'USD')
var cli_money = outputs.totals.money + ' ' + cli_currency
var cli_tax = outputs.totals.tax + ' ' + cli_currency
var cli_total = outputs.totals.total + ' ' + cli_currency
if (Conjuror.args.options.formats) {
console.log(chalk.gray(line))
console.log(chalk.green('Output Formats: ' + Conjuror.args.options.formats.join(',')))
}
console.log(chalk.gray(line))
console.log(chalk.gray(outputs.cli))
console.log(chalk.gray(line))
console.log(chalk.green('Time worked: ' + cli_hours))
console.log(chalk.green('Cash earned: ' + cli_money))
console.log(chalk.green('Taxes added: ' + cli_tax))
console.log(chalk.green('Total amount: ' + cli_total))
console.log(chalk.gray(line))
// Get User
Conjuror.summonUser(function(user_data) {
if (user_data && user_data.error === undefined) {
Conjuror.castToHTML(outputs, user_data, resource)
} else {
Conjuror.castToHTML(outputs, undefined, resource)
}
// return callback for test purposes, and for future func?
if (callback) return callback()
})
})
}).catch(function(error) {
console.log(chalk.red("Error processing: "), error)
if (callback) return callback(Error)
})
}
// Load Schema
Conjuror.Grow = function(args) {
console.log(chalk.green('Conjuror is starting up'))
Conjuror.args = args
var schema_file = args.options.input
var dataPath = require('path').dirname(schema_file)
// Open JSON
read.load(schema_file, function(error, json_data) {
if (error) {
return console.error(chalk.red('Had error opening file'), error)
}
console.log(chalk.blue(' - Get item from schema'))
// If Schema Contains Multiple
_.each(json_data.resources, function(resource, key) {
console.log(chalk.blue(' - Processing resource: ' + resource.path))
// Open Data
Conjuror.Twirl(dataPath, resource)
})
})
}
module.exports = Conjuror