autocode
Version:
spec-driven code generator
871 lines (746 loc) • 32.5 kB
text/coffeescript
crystal = {
load: require './load'
}
colors = require 'colors'
crypto = require 'crypto'
cson = require 'cson-parser'
extend = require 'extend-combine'
findVersions = require 'find-versions'
fs = require 'fs'
error = require '../error'
handlebars = require 'handlebars'
merge = require 'merge'
mkdirp = require 'mkdirp'
mustache = require 'mustache'
path = require 'path'
readdir = require 'fs-readdir-recursive'
request = require 'sync-request'
season = require 'season'
semver = require 'semver'
skeemas = require 'skeemas'
userHome = require 'user-home'
yaml = require 'js-yaml'
force = false
loaded_modules = {}
imports = {}
requireFromString = (code, modules_path) ->
try
m = new module.constructor()
m.paths = ["#{modules_path}"]
m._compile code, modules_path
m.exports
catch e
code
loadModules = (modules, host) ->
for module_name of modules
module_version_query = modules[module_name]
if module_version_query == 'latest'
module_version = 'latest'
else
module_version = null
if module_version_query.match /^(\.|\/)/
module_versions_path = path.normalize module_version_query
if fs.existsSync module_versions_path
module_version = module_version_query
else
module_versions_path = path.normalize "#{userHome}/.autocode/module/#{host}/#{module_name}"
if fs.existsSync module_versions_path
module_versions = fs.readdirSync module_versions_path
for model_ver in module_versions
model_ver = semver.clean model_ver
if model_ver and semver.satisfies(model_ver, modules[module_name]) and (!module_version or semver.gt(model_ver, module_version))
module_version = model_ver
if !module_version
throw new Error "No matches for Module (#{module_name}) with version (#{module_version_query}). Try: autocode update"
module_alias = module_name.substr(module_name.lastIndexOf('/') + 1)
if loaded_modules[module_name] and loaded_modules[module_name][module_version]
continue
if !loaded_modules[module_name]
loaded_modules[module_name] = {}
if module_version_query.match /^(\.|\/)/
module_path = module_version_query
else
module_path = "#{userHome}/.autocode/module/#{host}/#{module_name}/#{module_version}"
if !fs.existsSync module_path
throw new Error "Unknown module (#{module_name}) at version (#{module_version}). Try: autocode update"
module_config = crystal.load module_path
if !module_config
throw new Error "Unable to load configuration for module (#{module_name})"
if module_config.exports
for export_name of module_config.exports
exported = module_config.exports[export_name]
module_config.exports[export_name].dir = module_path
if typeof(exported.engine) == 'string'
export_path = path.normalize "#{module_path}/.autocode/engine/#{exported.engine}"
if fs.existsSync export_path
engine = require export_path
else
engine = requireFromString exported.engine, "#{module_path}/node_modules"
module_config.exports[export_name].engine = engine
if typeof(exported.helper) == 'string'
export_path = path.normalize "#{module_path}/.autocode/helper/#{exported.helper}"
if fs.existsSync export_path
helper = require export_path
else
helper = requireFromString exported.helper, "#{module_path}/node_modules"
module_config.exports[export_name].helper = helper
if typeof(exported.processor) == 'string'
export_path = path.normalize "#{module_path}/.autocode/processor/#{exported.processor}"
if fs.existsSync export_path
processor = require export_path
else
processor = requireFromString exported.processor, "#{module_path}/node_modules"
module_config.exports[export_name].processor = processor
if typeof(exported.schema) == 'string'
if exported.schema.match /^https?:/
schema = request 'get', exported.schema, { allowRedirectHeaders: ['User-Agent'] }
console.log schema
schema = yaml.safeLoad schema.body.toString()
else
export_path = path.normalize "#{module_path}/.autocode/schema/#{exported.schema}"
if fs.existsSync export_path
schema = yaml.safeLoad fs.readFileSync(export_path)
else if module_config.exports[exported.schema]
schema = module_config.exports[exported.schema].schema
else
schema = exported.schema
module_config.exports[export_name].schema = schema
if exported.spec instanceof Array
spec = {}
for exported_spec in exported.spec
export_path = "#{module_path}/.autocode/spec/#{exported_spec}"
if fs.existsSync export_path
exported_spec = yaml.safeLoad fs.readFileSync(export_path)
else
exported_spec = exported_spec
spec = extend true, true, spec, exported_spec
module_config.exports[export_name].spec = spec
else if typeof(exported.spec) == 'string' && exported.spec.match(/\./)
if exported.spec.match(/^https?:\/\//)
spec = request 'get', exported.spec, { allowRedirectHeaders: ['User-Agent'] }
module_config.exports[export_name].spec = yaml.safeLoad spec.body
else
export_path = "#{module_path}/.autocode/spec/#{exported.spec}"
if fs.existsSync export_path
spec = yaml.safeLoad fs.readFileSync(export_path)
else
spec = exported.spec
module_config.exports[export_name].spec = spec
if typeof(exported.template) == 'string'
export_path = path.normalize "#{module_path}/.autocode/template/#{exported.template}"
if fs.existsSync export_path
template = fs.readFileSync(export_path, 'utf8')
else
template = exported.template
module_config.exports[export_name].template = template
if typeof(exported.transformer) == 'string'
export_path = path.normalize "#{module_path}/.autocode/trans/#{exported.transformer}"
if fs.existsSync export_path
transformer = require export_path
else
transformer = requireFromString exported.transformer, "#{module_path}/node_modules"
module_config.exports[export_name].transformer = transformer
loaded_modules[module_name][module_version] = module_config
if module_config.imports
loadModules module_config.imports, module_config.host
else if module_config.modules
loadModules module_config.modules, module_config.host
loaded_modules = sortObject(loaded_modules)
processModules = () ->
for module_name of loaded_modules
module_versions = loaded_modules[module_name]
for version_name of module_versions
loaded_module = module_versions[version_name]
submodules = {}
if loaded_module.imports
loaded_module.modules = loaded_module.imports
loaded_module.imports = {}
for submodule_name of loaded_module.modules
submodule_alias = submodule_name.substr(submodule_name.lastIndexOf('/') + 1)
submodule_version_query = loaded_module.modules[submodule_name]
if submodule_version_query == 'latest'
submodule_version = 'latest'
else
submodule_version = null
if submodule_version_query.match /^(\.|\/)/
submodule_versions_path = path.normalize submodule_version_query
if fs.existsSync submodule_versions_path
submodule_version = submodule_version_query
else
submodule_versions_path = path.normalize "#{userHome}/.autocode/module/#{loaded_module.host}/#{submodule_name}"
if fs.existsSync submodule_versions_path
submodule_versions = fs.readdirSync submodule_versions_path
for model_ver in submodule_versions
model_ver = semver.clean model_ver
if model_ver and semver.satisfies(model_ver, submodule_version_query) and (!submodule_version or semver.gt(model_ver, submodule_version))
submodule_version = model_ver
if !submodule_version
throw new Error "No matches for submodule (#{submodule_name}) with version (#{submodule_version_query}). Try: autocode update"
submodule_exports = loaded_modules[submodule_name][submodule_version].exports
for submodule_export_name of submodule_exports
submodule_export = submodule_exports[submodule_export_name]
loaded_module.imports["#{submodule_alias}.#{submodule_export_name}"] = "#{submodule_name}.#{submodule_export_name}"
submodules[submodule_name] = submodule_version
for export_name of loaded_module.exports
exported = loaded_module.exports[export_name]
if exported.copy and typeof(exported.copy.engine) == 'string'
test = loaded_module.imports[exported.copy.engine].split('.')
test2 = test.pop()
test = test.join('.')
loaded_modules[module_name][version_name].exports[export_name].copy.engine = loaded_modules[test][submodules[test]].exports[test2].engine
if exported.copy and exported.copy.dest and exported.copy.dest.engine and typeof(exported.copy.dest.engine) == 'string'
test = loaded_module.imports[exported.copy.dest.engine].split('.')
test2 = test.pop()
test = test.join('.')
loaded_modules[module_name][version_name].exports[export_name].copy.dest.engine = loaded_modules[test][submodules[test]].exports[test2].engine
if typeof(exported.engine) == 'string' and loaded_module.imports[exported.engine]
test = loaded_module.imports[exported.engine].split('.')
test2 = test.pop()
test = test.join('.')
loaded_modules[module_name][version_name].exports[export_name].engine = loaded_modules[test][submodules[test]].exports[test2].engine
if exported.filename and typeof(exported.filename.engine) == 'string'
if !loaded_module.imports[exported.filename.engine]
throw new Error "Import does not exist for alias (#{exported.filename.engine})"
test = loaded_module.imports[exported.filename.engine].split('.')
test2 = test.pop()
test = test.join('.')
loaded_modules[module_name][version_name].exports[export_name].filename.engine = loaded_modules[test][submodules[test]].exports[test2].engine
if typeof(exported.helper) == 'object'
helpers = []
for helper_name of exported.helper
helper = exported.helper[helper_name]
if !loaded_module.imports[helper] and !loaded_module.exports[helper]
throw new Error "Import does not exist for alias (#{helper})"
if loaded_module.exports[helper]
helpers.push {
callback: loaded_module.exports[helper].helper
name: helper_name
}
else
test = loaded_module.imports[helper].split('.')
test2 = test.pop()
test = test.join('.')
helpers.push {
callback: loaded_modules[test][submodules[test]].exports[test2].helper
name: helper_name
}
loaded_modules[module_name][version_name].exports[export_name].helper = helpers
else if typeof(exported.helper) == 'string' and loaded_module.imports[exported.helper]
test = loaded_module.imports[exported.helper].split('.')
test2 = test.pop()
test = test.join('.')
if !loaded_modules[test][submodules[test]].exports[test2]
throw new Error "Import (#{test2}) does not exist for module (#{test})"
loaded_modules[module_name][version_name].exports[export_name].helper = [{
callback: loaded_modules[test][submodules[test]].exports[test2].helper
name: loaded_modules[test][submodules[test]].exports[test2].name
}]
if typeof(exported.schema) == 'string' and loaded_module.imports[exported.schema]
test = loaded_module.imports[exported.schema].split('.')
test2 = test.pop()
test = test.join('.')
loaded_modules[module_name][version_name].exports[export_name].schema = loaded_modules[test][submodules[test]].exports[test2].schema
if typeof(exported.transformer) == 'string' and loaded_module.imports[exported.transformer]
test = loaded_module.imports[exported.transformer].split('.')
test2 = test.pop()
test = test.join('.')
loaded_modules[module_name][version_name].exports[export_name].transformer = loaded_modules[test][submodules[test]].exports[test2].transformer
loadOutputs = (outputs, imports, config) ->
if !imports
throw new Error 'No imports available for output'
if !(outputs instanceof Array)
outputs = sortObject outputs
for output_i of outputs
output = outputs[output_i]
if !(outputs instanceof Array)
if output.filename == true
delete output.filename
else if output.filename and output.filename.engine and imports[output.filename.engine]
output.filename = {
engine: imports[output.filename.engine]
value: output_i
}
else if !output.filename
output.filename = output_i
if output.generator and !imports[output.generator]
throw new Error "Generator (#{output.generator}) does not exist for output in config (#{config.name})"
generator = imports[output.generator] or {}
output_processor = output.processor
generator_processor = generator.processor
output_processor = loadProcessor output_processor, generator_processor, imports
spec = {}
if output.spec
if output.spec instanceof Array
for output_spec in output.spec
if typeof(output_spec) == 'string'
if imports[output_spec]
output_processor = loadProcessor imports[output_spec].processor, null, imports
output_spec = imports[output_spec].spec
else if output_spec.match(/^https?:\/\//)
output_spec = request 'get', output_spec, { allowRedirectHeaders: ['User-Agent'] }
output_spec = yaml.safeLoad spec.body
else
spec_filename = ".autocode/spec/#{output_spec}"
if !fs.existsSync spec_filename
throw new Error "File (#{spec_filename}) does not exist for spec in output for config (#{config.id})"
output_spec = yaml.safeLoad fs.readFileSync(spec_filename, 'utf8')
output_spec = parse output_spec, config, output_processor
spec = extend true, true, spec, output_spec
else
if typeof(output.spec) == 'object'
spec = output.spec
else if typeof(output.spec) == 'string'
if imports[output.spec]
output_processor = loadProcessor imports[output.spec].processor, null, imports
spec = imports[output.spec].spec
else if output.spec.match(/^https?:\/\//)
spec = request 'get', output.spec, { allowRedirectHeaders: ['User-Agent'] }
spec = yaml.safeLoad spec.body
else
spec_filename = ".autocode/spec/#{output.spec}"
if !fs.existsSync spec_filename
throw new Error "File (#{spec_filename}) does not exist for spec in output for config (#{config.id})"
spec = yaml.safeLoad fs.readFileSync(spec_filename, 'utf8')
spec = parse spec, config, output_processor
if generator.schema
gen_schema = generator.schema
if typeof(gen_schema) == 'string'
gen_name = output.generator.split '.'
if !gen_name.length
throw new Error "Invalid generator name: #{gen_name}"
if gen_name.length > 1
gen_prefix = gen_name[0]
gen_suffix = gen_name[1]
if !imports["#{gen_prefix}.#{gen_suffix}"] or !imports["#{gen_prefix}.#{gen_suffix}"].schema
throw new Error "Schema does not exist for: #{gen_prefix}.#{gen_suffix}"
gen_schema = imports["#{gen_prefix}.#{gen_suffix}"].schema
else
gen_name = gen_name[0]
if !imports["#{gen_name}"] or !imports["#{gen_name}"].schema
throw new Error "Schema does not exist for: #{gen_name}"
gen_schema = imports["#{gen_name}"].schema
if typeof(gen_schema) == 'string'
schema_name = gen_schema.split '.'
if !schema_name.length
throw new Error "Invalid schema: #{schema_name}"
if schema_name.length > 1
schema_prefix = schema_name[0]
schema_suffix = schema_name[1]
if !imports["#{schema_prefix}.#{schema_suffix}"] or !imports["#{schema_prefix}.#{schema_suffix}"].schema
throw new Error "Schema does not exist for: #{schema_prefix}.#{schema_suffix}"
gen_schema = imports["#{schema_prefix}.#{schema_suffix}"].schema
else
schema_name = schema_name[0]
if !imports["#{schema_name}"] or !imports["#{schema_name}"].schema
throw new Error "Schema does not exist: #{schema_name}"
gen_schema = imports["#{schema_name}"].schema
gen_schema = loadSchemaRefs gen_schema
validate = skeemas.validate spec, gen_schema
if !validate.valid
console.log validate.errors
console.log("ERROR: Specification failed validation.")
for error in validate.errors
console.log "- #{error.message} for specification (#{error.context.substr(2)}) in generator (#{output.generator})"
throw new Error "ERROR: Invalid specification."
engine = output.engine or generator.engine
if typeof(engine) == 'string'
if !imports[engine] or !imports[engine].engine
throw new Error "Engine was not imported (#{engine}) at path #{config.path}"
engine = imports[engine].engine
helpers = if generator.helper then generator.helper else null
injectors = if output.injector then output.injector else null
if generator.copy
if typeof(generator.copy) == 'object'
if !generator.copy.src
throw new Error "Copy requires source"
copy_src = generator.copy.src
else if typeof(generator.copy) == 'string'
copy_src = generator.copy
else
throw new Error "Invalid value for copy"
if generator.copy.dest
if typeof(generator.copy.dest) == 'object'
if !generator.copy.dest.engine
throw new Error "Destination engine is required for copy"
if !generator.copy.dest.value
throw new Error "Destination value is required for copy"
copy_dest = generator.copy.dest.engine spec, generator.copy.dest.value, helpers
else if typeof(generator.copy.dest) == 'string'
copy_dest = generator.copy.dest
else
throw new Error "Invalid Destination for copy"
if copy_dest.substr(copy_dest.length - 1) != '/'
copy_dest += '/'
else
copy_dest = ''
copy_dir = "#{generator.dir}/.autocode/#{copy_src}"
if !fs.existsSync copy_dir
throw new Error "Directory (#{copy_dir}) does not exist in copy"
code_files = readdir copy_dir, (x) ->
return true
for code_file in code_files
copy_filename = "#{output.path}/#{copy_dest}#{code_file}"
code_dir = copy_filename.substring 0, copy_filename.lastIndexOf('/')+1
if !fs.existsSync code_dir
mkdirp.sync code_dir
fs.writeFileSync "#{copy_filename}", fs.readFileSync "#{copy_dir}/#{code_file}"
console.log "Generated file: #{copy_filename}".green
generator_filename = output.filename or generator.filename
if !generator_filename
continue
iterator = output.iterator or generator.iterator
if iterator
if !spec[iterator]
throw new Error "Iterator (#{iterator}) not found in spec for generator (#{generator}) in config (#{config.id})"
files = []
if spec[iterator] instanceof Array
for file in spec[iterator]
files.push file
else if typeof(spec[iterator]) == 'object'
for name of spec[iterator]
files.push name
else
files = [generator_filename]
for i of files
file = files[i]
if file.name
filename_options = {
name: file.name
}
file = file.name
else
filename_options = []
filename_options[0] = {}
filename_options[0][file] = spec
file_name = null
if generator_filename.engine
filename = generator_filename.engine filename_options, generator_filename.value, helpers
else
filename = generator_filename
if output.path
if !fs.existsSync output.path
mkdirp output.path
filename = "#{output.path}/#{filename}"
if !spec
throw new Error "Spec is required."
if output.template
output_path = ".autocode/template/#{output.template}"
if fs.existsSync output_path
template = fs.readFileSync(output_path, 'utf8')
else
template = output.template
else
template = generator.template
if engine
if iterator
content_spec = extend true, true, {}, spec[iterator][i] or spec[iterator][file]
if content_spec
content_spec = extend true, true, content_spec, spec
content_spec.name = file
if content_spec and content_spec[iterator] and content_spec[iterator][file] and content_spec[iterator][file]['$injector']
template = inject template, injectors, false
template = inject template, content_spec[iterator][file]['$injector'], false
template = inject template, null, true
else
template = inject template, injectors, true
content = engine content_spec, template, helpers, process.cwd()
else
if template
template = inject template, injectors, true
content = engine spec, template, helpers, process.cwd()
else if template
template = inject template, injectors, true
content = template
else if spec
content = spec
else
content = ""
transformer = output.transformer or generator.transformer
if transformer
if typeof(transformer) == 'string'
if !imports[transformer]
throw new Error "Transformer #{transformer} does not exist"
transformer = imports[transformer].transformer
content = transformer content
else if typeof(content) == 'object'
content = ""
cache_filename = ".autocode/cache.yml"
if fs.existsSync cache_filename
cache = yaml.safeLoad fs.readFileSync(cache_filename, 'utf8')
if !cache.checksum
cache.checksum = {}
else
cache = {
checksum: {}
}
filename_checksum = crypto.createHash('md5').update(filename, 'utf8').digest('hex')
if cache.checksum[filename_checksum]
if !fs.existsSync filename
if force != true
throw new Error "ERROR: File (#{filename}) has been manually deleted outside of Crystal. Use -f to force code generation and overwrite this deletion.".red.bold
else
cache_checksum = cache.checksum[filename_checksum]
file_checksum = crypto.createHash('md5').update(fs.readFileSync(filename, 'utf8'), 'utf8').digest('hex')
if cache_checksum != file_checksum && force != true
throw new Error "ERROR: File (#{filename}) has been manually changed outside of Crystal. Use -f to force code generation and overwrite these changes.".red.bold
content_checksum = crypto.createHash('md5').update(content, 'utf8').digest('hex')
cache.checksum[filename_checksum] = content_checksum
if !fs.existsSync ".autocode"
mkdirp.sync ".autocode"
fs.writeFileSync ".autocode/cache.yml", yaml.safeDump cache
file_last_path = filename.lastIndexOf('/')
if file_last_path != -1
mkdirp.sync filename.substr(0, file_last_path)
fs.writeFileSync filename, content
console.log "- #{filename}"
loadProcessor = (output_processor, generator_processor, imports) ->
if generator_processor
if typeof(output_processor) == 'string'
output_processor = [output_processor]
if typeof(generator_processor) == 'string'
generator_processor = [generator_processor]
if output_processor && generator_processor
output_processor = extend true, true, output_processor, generator_processor
else
output_processor = generator_processor
if output_processor instanceof Array
processors = []
for processor in output_processor
if !imports[processor]
throw new Error "Import does not exist for alias (#{processor})"
processors.push {
alias: processor
callback: imports[processor].processor
}
output_processor = processors
else if typeof(output_processor) == 'string'
if !imports[output_processor]
throw new Error "Import does not exist for alias (#{output_processor})"
output_processor = [{
alias: output_processor
callback: imports[output_processor].processor
}]
output_processor
loadSchemaRefs = (schema) ->
if schema['$ref'] and !!schema['$ref'].match(/^http:\/\//)
schema_path = path.join userHome, '.autocode/schema/', schema['$ref'].replace(/(^http:\/\/|
if !fs.existsSync schema_path
console.log "Loading schema for ref: #{schema['$ref']}"
resp = request 'get', schema['$ref'], { allowRedirectHeaders: ['User-Agent'] }
if !fs.existsSync path.dirname(schema_path)
mkdirp.sync path.dirname(schema_path)
fs.writeFileSync schema_path, resp
schema = yaml.safeLoad(fs.readFileSync(schema_path))
return schema
if schema.properties
for property_name of schema.properties
if schema.properties[property_name].properties
schema.properties[property_name] = loadSchemaRefs schema.properties[property_name]
else
schema.properties[property_name] = loadSchemaRefs schema.properties[property_name]
if schema.definitions
for property_name of schema.definitions
if schema.definitions[property_name].definitions
schema.definitions[property_name] = loadSchemaRefs schema.definitions[property_name]
else
schema.definitions[property_name] = loadSchemaRefs schema.definitions[property_name]
schema
generate = (opts) ->
loaded_modules = {}
config = this.config
if opts and opts.force
force = true
if config.imports
config.modules = config.imports
if config.name
config.modules[config.name] = process.cwd()
delete config.imports
if config.modules
loadModules config.modules, config.host
processModules()
for module_name of config.modules
module_alias = module_name.substr(module_name.lastIndexOf('/') + 1)
module_version_query = config.modules[module_name]
if module_version_query == 'latest'
module_version = 'latest'
else
module_version = null
if module_version_query.match /^(\.|\/)/
module_versions_path = path.normalize module_version_query
if !fs.existsSync module_versions_path
throw new Error "Module (#{module_name}) not found at local path (#{module_version_query})."
else
module_version = module_version_query
else
module_versions_path = path.normalize "#{userHome}/.autocode/module/#{config.host}/#{module_name}"
if fs.existsSync module_versions_path
module_versions = fs.readdirSync module_versions_path
for model_ver in module_versions
model_ver = semver.clean model_ver
if model_ver and semver.satisfies(model_ver, config.modules[module_name]) and (!module_version or semver.gt(model_ver, module_version))
module_version = model_ver
if !module_version
throw new Error "No matches for Module (#{module_name}) with version (#{module_version_query}). Try: autocode update"
exports = loaded_modules[module_name][module_version].exports
for export_name of exports
exported = exports[export_name]
if module_alias == config.name
imports["#{export_name}"] = exported
imports["#{module_alias}.#{export_name}"] = exported
if config.outputs
console.log "Generating outputs...".bold
loadOutputs config.outputs, imports, config
{
imports: imports
}
inject = (template, injectors, remove_injector = true) ->
template.replace /([ |\t]+)?>>>[a-z_]*<<<\n?/ig, (injector) ->
injector_tabs = injector.match /^[\s]+>/g
if injector_tabs
injector_tabs = injector_tabs[0].substr(0, injector_tabs[0].length-1)
else
injector_tabs = ''
injector = injector.replace /[\s]+/g, ''
injector = injector.substr 3, injector.length-6
if injectors and injectors[injector]
if injectors[injector] instanceof Array
injected = injectors[injector].join "\n"
else if (injectors[injector].substr(0,1) == '/' or injectors[injector].substr(0,2) == './') and fs.existsSync ".autocode/#{injectors[injector]}"
injected = fs.readFileSync ".autocode/#{injectors[injector]}", 'utf8'
else
injected = injectors[injector]
if remove_injector == true
inject_final = ''
else
inject_final = "#{injector_tabs}>>>#{injector}<<<\n"
for inj in injected.split "\n"
inject_final += "#{injector_tabs}#{inj}\n"
inject_final += ""
else if remove_injector == true
''
else
"#{injector_tabs}>>>#{injector}<<<\n"
parse = (spec, config, processors) ->
if spec['$processor']
if typeof(spec['$processor']) == 'string'
spec['$processor'] = [spec['$processor']]
for proc in spec['$processor']
for processor in processors
if processor.alias == proc
spec['$value'] = processor.callback spec['$value']
return spec['$value']
else if typeof(spec['$value']) == 'string' and imports[spec['$value']] and imports[spec['$value']].spec
if spec['$key']
return imports[spec['$value']].spec[spec['$key']]
else
return imports[spec['$value']].spec
for i of spec
s = spec[i]
if s == undefined
continue
if typeof(s) == 'object' and !s['$processor']
spec[i] = parse(spec[i], config, processors)
else if typeof(s) == 'string' && s.substr(0, 1) == '$' && config[s.substr(1)]
spec[i] = config[s.substr(1)]
if spec[i]['$processor']
if typeof(spec[i]['$processor']) == 'string'
spec[i]['$processor'] = [spec[i]['$processor']]
for proc in spec[i]['$processor']
if processors
for processor in processors
if processor.alias == proc
if typeof(spec[i]['$value']) == 'string'
if imports[spec[i]['$value']]
spec[i]['$value'] = imports[spec[i]['$value']].spec
else if spec[i]['$value'].match(/^https?:\/\//)
spec[i]['$value'] = request 'get', spec[i]['$value'], { allowRedirectHeaders: ['User-Agent'] }
spec[i]['$value'] = yaml.safeLoad spec[i]['$value'].body
spec[i]['$value'] = processor.callback spec[i]['$value']
spec[i] = spec[i]['$value']
spec
sortObject = (object) ->
Object.keys(object).sort().reduce ((result, key) ->
result[key] = object[key]
result
), {}
module.exports = generate