flownote
Version:
FlowNote lets developers create, organize, and reason about event-oriented applications with a simple flow-based language.
633 lines (546 loc) • 16.3 kB
JavaScript
import Action from '../src/action'
import StandardChannel from '../src/channels/standardChannel'
import ErrorChannel from '../src/channels/errorChannel'
import NamedChannel from '../src/channels/namedChannel'
import StandardNode from '../src/nodes/standardNode'
import StandardMilestone from '../src/nodes/standardMilestone'
import Flow from '../src/flow'
const fs = require('fs')
const path = require('path')
class Generator {
/**
* [constructor description]
* @param {[type]} application [description]
* @return {[type]} [description]
*/
constructor (application) {
this.application = application
this.nodeFactories = {}
this.nodeAliases = {}
}
/**
* [getNodeInstance description]
* @param {[type]} nodeName [description]
* @return {[type]} [description]
*/
getNodeInstance (nodeName) {
const config = this.nodeFactories[nodeName] || {
tags: [],
actions: []
}
return new StandardNode(this.application, undefined, nodeName, undefined, config.tags, config.actions)
}
/**
* [FlowTypes description]
* @param {[type]} flow [description]
*/
FlowTypes (flow) {
// console.log('FlowTypes')
return flow.eval()
}
/**
* [NodeTypes description]
* @param {[type]} node [description]
*/
NodeTypes (node) {
// console.log('NodeTypes')
return node.eval()
}
/**
* [PathTypes description]
* @param {[type]} path [description]
*/
PathTypes (path) {
// console.log('PathTypes')
return path.eval()
}
/**
* [Expression description]
* @param {[type]} line [description]
*/
Expression (line) {
// console.log('Expression')
return line.eval()
}
/**
* [FlowDefinition description]
* @param {[type]} flowName [description]
* @param {[type]} httpMethod [description]
* @param {[type]} httpEndpoint [description]
* @param {[type]} config [description]
* @param {[type]} path [description]
*/
FlowDefinition (flowName, httpMethod, httpEndpoint, config, path) {
// console.log('FlowDefinition')
const method = httpMethod.eval()
const endpoint = '/' + httpEndpoint.eval().join('/')
if (this.application.getFlowByHttp(method, endpoint)) {
throw new Error(`Flow definition already exists for the ${method} ${endpoint} endpoint.`)
}
const flow = new Flow(this.application, undefined, flowName.eval(), config.eval(), undefined, method, endpoint, undefined)
const pathChain = path.eval()
const rootNode = pathChain[0]
let lastStep = pathChain[0]
for (var i = 1, len = pathChain.length; i < len; i++) {
const step = pathChain[i]
if (step !== undefined) {
if (lastStep instanceof StandardNode) {
// When dealing with milestones, we have to change the lastStep to the milestone
const milestone = lastStep.hasMilestone()
if (milestone) {
lastStep = milestone
}
}
lastStep.connect(step)
lastStep = step
}
}
flow.connect(rootNode)
this.application.registerFlow(flow)
}
/**
* [NodeDefinition description]
* @param {[type]} nodeName [description]
* @param {[type]} properties [description]
* @param {[type]} actions [description]
*/
NodeDefinition (nodeLabel, properties, actions) {
// console.log('NodeDefinition')
const nodeName = nodeLabel.eval()
if (this.nodeAliases[nodeName]) {
return this.nodeAliases[nodeName]
}
if (this.nodeFactories[nodeName] === undefined) {
this.nodeFactories[nodeName] = {
tags: [ /* @TODO */ ],
config: properties.eval(),
actions: actions.eval()
}
} else {
throw new Error(`Node definition already exists for the ${nodeName}.`)
}
}
/**
* [Actions description]
* @param {[type]} actions [description]
*/
Actions (actions) {
// console.log('Actions')
const list = actions.eval()
const result = []
list.forEach(actionLabel => {
if (actionLabel !== undefined) {
const action = this.application.requireAction(actionLabel, new Function(`return function () {
// @TODO Fill out this stub
}`)())
result.push(action)
}
})
return result
}
/**
* [Path description]
* @param {[type]} nodeName [description]
* @param {[type]} channel [description]
* @param {[type]} path [description]
*/
Path (node, channel, path) {
// console.log('Path')
const rootNode = node.eval()
const channelInstance = channel.eval()
const steps = path.eval()
let lastStep = channelInstance
// console.log('> Pathing out', rootNode.name, steps.length)
steps.forEach(step => {
if (step !== undefined) {
if (lastStep instanceof StandardNode) {
// When dealing with milestones, we have to change the lastStep to the milestone
const milestone = lastStep.hasMilestone()
if (milestone) {
lastStep = milestone
}
}
lastStep.connect(step)
lastStep = step
}
})
// console.log('>>> finalizing', channelInstance.name, 'to', rootNode.name)
rootNode.connect(channelInstance)
return rootNode
}
/**
* [NonemptyListOf description]
* @param {[type]} token [description]
* @param {[type]} separator [description]
* @param {[type]} tokens [description]
* @return {[type]} [description]
*/
NonemptyListOf (token, separator, tokens) {
// console.log('NonemptyListOf')
const tokenInstance = token.eval()
const type = token.ctorName
if (tokenInstance === undefined) {
// Blank line
return []
}
if (type === 'Nodes') {
// Dealing with a Path list
const separatorList = separator.eval()
const tokensList = tokens.eval()
const result = [ tokenInstance ]
for (var i = 0, len = separatorList.length; i < len; i++) {
result.push(separatorList[i])
result.push(tokensList[i])
}
return result
} else if (type === 'Property') {
// Properties
return tokenInstance.concat(tokens.eval())
} else if (type === 'label' || type === 'Axiom') {
// Actions or Axioms
return [ tokenInstance ].concat(tokens.eval())
} else {
throw new Error('Unknown ctorName: ' + type)
}
}
/**
* [Import description]
* @param {[type]} fileName [description]
* @param {[type]} extension [description]
*/
Import (filenameToken, compiler, namespaceToken) {
const filename = filenameToken.eval()
const isUrl = filename.indexOf('//') > -1
if (isUrl) {
// @TODO
} else {
const extension = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase()
if (extension === 'js' || extension === 'mjs') {
// Import actions
let actions = require(`${process.cwd()}/${filename}`)
if (actions.default) {
actions = actions.default
}
const namespace = namespaceToken.eval()[0]
if (!namespace) {
throw new Error(`Importing ${filename} requires a namespace`)
}
this.application.actionGenerators.push(actions.toString())
actions.call(this.application, require).forEach(action => {
this.application.registerAction(`${namespace}.${action.name}`, action)
})
} else if (extension === 'flow') {
// Import flow
const namespace = namespaceToken.eval()[0]
if (!namespace) {
throw new Error(`Importing ${filename} requires a namespace`)
}
const contents = fs.readFileSync(`${process.cwd()}/${filename}`).toString()
compiler.compile(contents, namespace)
} else {
// Use node_modules
// Import Actions file
const packageJson = require(`${process.cwd()}/node_modules/${filename}/package.json`)
const main = packageJson.main || 'index.js'
let actions = require(`${process.cwd()}/node_modules/${filename}/${main}`)
if (actions.default) {
actions = actions.default
}
this.application.actionGenerators.push(actions.toString())
actions.call(this.application, require).forEach(action => {
this.application.registerAction(`${filename}.${action.name}`, action)
})
// Import Flow file
const flowDir = path.dirName(`${process.cwd()}/node_modules/${filename}/${main}`)
const flowFile = fs.readFileSync(`${flowDir}/index.flow`).toString()
compiler.compile(flowFile, filename)
}
}
}
/**
* [Nodes description]
* @param {[type]} node [description]
*/
Nodes (node) {
// console.log('Nodes')
return node.eval()
}
/**
* [LinguisticNodes description]
* @param {[type]} node [description]
*/
LinguisticNodes (node) {
// console.log('LinguisticNodes')
return node.eval()
}
/**
* [Milestone description]
* @param {[type]} nodeName [description]
*/
Milestone (nodeName) {
const node = nodeName.eval()
const channel = new StandardChannel(this.application, undefined, 'Plain', undefined, [])
const milestone = new StandardMilestone(this.application, undefined, `Milestone`, 'fcfs', [], [])
milestone.config.silent = node.config.silent
node.connect(channel)
channel.connect(milestone)
return node
}
/**
* [LinguisticMilestone description]
* @param {[type]} nodeName [description]
*/
LinguisticMilestone (nodeName) {
// console.log('LinguisticMilestone')
return new StandardMilestone(this.application, undefined, `Milestone`, 'fcfs', [], [])
}
/**
* [Node description]
* @param {[type]} node [description]
*/
Node (node) {
// console.log('Node')
return node.eval()
}
/**
* [WaitFor description]
* @param {[type]} nodeName [description]
* @param {[type]} waitFor [description]
*/
WaitFor (nodeName, waitFor) {
const node = nodeName.eval()
const waitForAction = waitFor.eval()
const action = new Action(`waitFor${waitForAction}`, async function () {
await this.waitFor(waitForAction)
}, this.application)
node.addAction(action, node.actions.length - 1)
return node
}
/**
* [NodeBase description]
* @param {[type]} node [description]
*/
NodeBase (node) {
// console.log('NodeBase')
return node.eval()
}
/**
* [SilentNode description]
* @param {[type]} nodeName [description]
*/
SilentNode (node) {
// console.log('SilentNode')
const nodeInstance = node.eval()
if (this.nodeAliases[nodeInstance.name]) {
throw new Error('Cannot modify labeled a Path root.')
}
nodeInstance.config.silent = true
return nodeInstance
}
/**
* [IdentityNode description]
* @param {[type]} nodeName [description]
* @param {[type]} aliasLabel [description]
*/
IdentityNode (node, aliasLabel) {
// console.log('IdentityNode')
const nodeInstance = node.eval()
const alias = aliasLabel.eval()
if (!this.nodeAliases[alias]) {
this.nodeAliases[alias] = nodeInstance
}
return this.nodeAliases[alias]
}
/**
* [StandardNode description]
* @param {[type]} nodeName [description]
*/
StandardNode (nodeLabel) {
const name = nodeLabel.eval()
if (this.nodeAliases[name]) {
return this.nodeAliases[name]
}
return this.getNodeInstance(name)
}
/**
* [Concept description]
* @param {[type]} words [description]
*/
Concept (words) {
// console.log('Concept')
const result = []
words.forEach(word => {
if (word !== undefined) {
result.push(word.eval())
}
})
return result.join(' ')
}
/**
* [Channel description]
* @param {[type]} channel [description]
*/
Channel (channel) {
// console.log('Channel')
return channel.eval()
}
/**
* [ErrorChannel description]
* @param {[type]} channelName [description]
* @param {[type]} properties [description]
*/
ErrorChannel (channelName, properties) {
// console.log('ErrorChannel')
const name = channelName.eval()
const props = properties.eval()
return new ErrorChannel(this.application, undefined, name, undefined, [ name ], props.retry, props.retryDelay, [])
}
/**
* [PlainChannel description]
* @param {[type]} properties [description]
*/
PlainChannel (properties) {
// console.log('PlainChannel')
const props = properties.eval()
return new StandardChannel(this.application, undefined, 'Plain', undefined, [], props.retry, props.retryDelay, [])
}
/**
* [NamedChannel description]
* @param {[type]} channelName [description]
* @param {[type]} properties [description]
*/
NamedChannel (channelName, properties) {
// console.log('NamedChannel')
const name = channelName.eval()
const props = properties.eval()
return new NamedChannel(this.application, undefined, name, undefined, [ name ], props.retry, props.retryDelay, [])
}
/**
* [Properties description]
* @param {[type]} properties [description]
*/
Properties (properties) {
// console.log('Properties')
const result = {}
const propertiesInstance = properties.eval()
propertiesInstance.forEach(property => {
if (property !== undefined) {
result[property[0]] = property[1]
}
})
return result
}
/**
* [Property description]
* @param {[type]} key [description]
* @param {[type]} value [description]
*/
Property (key, value) {
// console.log('Property')
return [key.eval(), value.eval()]
}
/**
* [HttpMethods description]
* @param {[type]} method [description]
*/
HttpMethods (method) {
// console.log('HttpMethods')
return method.eval()
}
/**
* [Axiom description]
* @param {[type]} axiom [description]
* @return {[type]} [description]
*/
Axiom (axiom, namespace) {
// console.log('axiom')
if (namespace) {
return `${namespace}.${axiom.eval().join('.')}`
} else {
return axiom.eval().join('.')
}
}
/**
* [label description]
* @param {[type]} label [description]
* @return {[type]} [description]
*/
label (label) {
// console.log('label')
return label.eval().join('')
}
/**
* [string description]
* @param {[type]} string [description]
* @return {[type]} [description]
*/
string (str) {
// console.log('string')
return str.sourceString
}
/**
* [number description]
* @return {[type]} [description]
*/
number (number) {
// console.log('number')
return number.eval()
}
/**
* [number description]
* @param {[type]} whole [description]
* @param {[type]} dot [description]
* @param {[type]} decimal [description]
* @return {[type]} [description]
*/
fraction (whole, dot, decimal) {
// console.log('fraction')
const num1 = whole.eval()
const num2 = '0.' + decimal.eval()
return (Number)(num1) + (Number)(num2)
}
whole (number) {
// console.log('whole')
return (Number)(number.eval())
}
/**
* [space description]
* @param {[type]} space [description]
* @return {[type]} [description]
*/
space (space) {
// console.log('space')
return space.eval()
}
/**
* [comment description]
* @param {[type]} comments [description]
* @return {[type]} [description]
*/
comment (comments) {
// console.log('comment')
return comments.eval()
}
/**
* [multiLineComment description]
* @param {[type]} _1 [description]
* @param {[type]} comments [description]
* @param {[type]} _2 [description]
* @return {[type]} [description]
*/
multiLineComment (comments) {
// console.log('multiLineComment')
return comments.eval()
}
/**
* [singleLineComment description]
* @param {[type]} _1 [description]
* @param {[type]} comments [description]
* @return {[type]} [description]
*/
singleLineComment (_1, comments) {
// console.log('singleLineComment')
return comments.eval()
}
}
export { Generator as default }