This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
8.4 KiB
302 lines
8.4 KiB
'use strict'; |
|
|
|
var fs = require('fs'); |
|
var path = require('path'); |
|
var babylon = require('babylon'); |
|
var t = require('babel-types'); |
|
var generate = require('babel-generator').default; |
|
var traverse = require('babel-traverse').default; |
|
var resolve = require('resolve'); |
|
|
|
var camelToDashed = require('../lib/parsers').camelToDashed; |
|
|
|
var basename = path.basename; |
|
var dirname = path.dirname; |
|
|
|
var uniqueIndex = 0; |
|
function getUniqueIndex() { |
|
return uniqueIndex++; |
|
} |
|
|
|
var property_files = fs.readdirSync(path.resolve(__dirname, '../lib/properties')).filter(function (property) { |
|
return property.substr(-3) === '.js'; |
|
}); |
|
var out_file = fs.createWriteStream(path.resolve(__dirname, '../lib/properties.js'), {encoding: 'utf-8'}); |
|
|
|
out_file.write('\'use strict\';\n\n// autogenerated\n\n'); |
|
out_file.write('/*\n *\n * http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties\n */\n\n'); |
|
|
|
function isModuleDotExports(node) { |
|
return ( |
|
t.isMemberExpression(node, {computed: false}) && |
|
t.isIdentifier(node.object, {name: 'module'}) && |
|
t.isIdentifier(node.property, {name: 'exports'}) |
|
); |
|
} |
|
function isRequire(node, filename) { |
|
if ( |
|
t.isCallExpression(node) && |
|
t.isIdentifier(node.callee, {name: 'require'}) && |
|
node.arguments.length === 1 && |
|
t.isStringLiteral(node.arguments[0]) |
|
) { |
|
var relative = node.arguments[0].value; |
|
var fullPath = resolve.sync(relative, {basedir: dirname(filename)}); |
|
return {relative: relative, fullPath: fullPath}; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
// step 1: parse all files and figure out their dependencies |
|
var parsedFilesByPath = {}; |
|
property_files.map(function (property) { |
|
var filename = path.resolve(__dirname, '../lib/properties/' + property); |
|
var src = fs.readFileSync(filename, 'utf8'); |
|
property = basename(property, '.js'); |
|
var ast = babylon.parse(src); |
|
var dependencies = []; |
|
traverse(ast, { |
|
enter(path) { |
|
var r; |
|
if (r = isRequire(path.node, filename)) { |
|
dependencies.push(r.fullPath); |
|
} |
|
} |
|
}); |
|
parsedFilesByPath[filename] = { |
|
filename: filename, |
|
property: property, |
|
ast: ast, |
|
dependencies: dependencies, |
|
}; |
|
}); |
|
|
|
// step 2: serialize the files in an order where dependencies are always above |
|
// the files they depend on |
|
var externalDependencies = []; |
|
var parsedFiles = []; |
|
var addedFiles = {}; |
|
function addFile(filename, dependencyPath) { |
|
if (dependencyPath.indexOf(filename) !== -1) { |
|
throw new Error( |
|
'Circular dependency: ' + |
|
dependencyPath.slice(dependencyPath.indexOf(filename)).concat([filename]).join(' -> ') |
|
); |
|
} |
|
var file = parsedFilesByPath[filename]; |
|
if (addedFiles[filename]) { |
|
return; |
|
} |
|
if (!file) { |
|
externalDependencies.push(filename); |
|
} else { |
|
file.dependencies.forEach(function (dependency) { |
|
addFile(dependency, dependencyPath.concat([filename])); |
|
}); |
|
parsedFiles.push(parsedFilesByPath[filename]); |
|
} |
|
addedFiles[filename] = true; |
|
} |
|
Object.keys(parsedFilesByPath).forEach(function (filename) { |
|
addFile(filename, []); |
|
}); |
|
// Step 3: add files to output |
|
// renaming exports to local variables `moduleName_export_exportName` |
|
// and updating require calls as appropriate |
|
var moduleExportsByPath = {}; |
|
var statements = []; |
|
externalDependencies.forEach(function (filename, i) { |
|
var id = t.identifier( |
|
'external_dependency_' + |
|
basename(filename, '.js').replace(/[^A-Za-z]/g, '') + |
|
'_' + i |
|
); |
|
moduleExportsByPath[filename] = {defaultExports: id}; |
|
var relativePath = path.relative(path.resolve(__dirname + '/../lib'), filename); |
|
if (relativePath[0] !== '.') { |
|
relativePath = './' + relativePath; |
|
} |
|
statements.push(t.variableDeclaration( |
|
'var', |
|
[ |
|
t.variableDeclarator( |
|
id, |
|
t.callExpression( |
|
t.identifier('require'), |
|
[ |
|
t.stringLiteral( |
|
relativePath |
|
) |
|
] |
|
) |
|
) |
|
] |
|
)); |
|
}); |
|
function getRequireValue(node, file) { |
|
var r; |
|
// replace require("./foo").bar with the named export from foo |
|
if ( |
|
t.isMemberExpression(node, {computed: false}) && |
|
(r = isRequire(node.object, file.filename)) |
|
) { |
|
var e = moduleExportsByPath[r.fullPath]; |
|
if (!e) { |
|
return; |
|
} |
|
if (!e.namedExports) { |
|
return t.memberExpression( |
|
e.defaultExports, |
|
node.property |
|
); |
|
} |
|
if (!e.namedExports[node.property.name]) { |
|
throw new Error(r.relative + ' does not export ' + node.property.name); |
|
} |
|
return e.namedExports[node.property.name]; |
|
|
|
// replace require("./foo") with the default export of foo |
|
} else if (r = isRequire(node, file.filename)) { |
|
var e = moduleExportsByPath[r.fullPath]; |
|
if (!e) { |
|
if (/^\.\.\//.test(r.relative)) { |
|
node.arguments[0].value = r.relative.substr(1); |
|
} |
|
return; |
|
} |
|
return e.defaultExports; |
|
} |
|
} |
|
parsedFiles.forEach(function (file) { |
|
var namedExports = {}; |
|
var localVariableMap = {}; |
|
|
|
traverse(file.ast, { |
|
enter(path) { |
|
// replace require calls with the corresponding value |
|
var r; |
|
if (r = getRequireValue(path.node, file)) { |
|
path.replaceWith(r); |
|
return; |
|
} |
|
|
|
// if we see `var foo = require('bar')` we can just inline the variable |
|
// representing `require('bar')` wherever `foo` was used. |
|
if ( |
|
t.isVariableDeclaration(path.node) && |
|
path.node.declarations.length === 1 && |
|
t.isIdentifier(path.node.declarations[0].id) && |
|
(r = getRequireValue(path.node.declarations[0].init, file)) |
|
) { |
|
var newName = 'compiled_local_variable_reference_' + getUniqueIndex(); |
|
path.scope.rename( |
|
path.node.declarations[0].id.name, |
|
newName |
|
); |
|
localVariableMap[newName] = r; |
|
path.remove(); |
|
return; |
|
} |
|
|
|
// rename all top level variables to keep them local to the module |
|
if ( |
|
t.isVariableDeclaration(path.node) && |
|
t.isProgram(path.parent) |
|
) { |
|
path.node.declarations.forEach(function (declaration) { |
|
path.scope.rename( |
|
declaration.id.name, |
|
file.property + '_local_var_' + declaration.id.name |
|
); |
|
}); |
|
return; |
|
} |
|
|
|
// replace module.exports.bar with a variable for the named export |
|
if ( |
|
t.isMemberExpression(path.node, {computed: false}) && |
|
isModuleDotExports(path.node.object) |
|
) { |
|
var name = path.node.property.name; |
|
var identifier = t.identifier(file.property + '_export_' + name); |
|
path.replaceWith(identifier); |
|
namedExports[name] = identifier; |
|
} |
|
} |
|
}); |
|
traverse(file.ast, { |
|
enter(path) { |
|
if ( |
|
t.isIdentifier(path.node) && |
|
Object.prototype.hasOwnProperty.call(localVariableMap, path.node.name) |
|
) { |
|
path.replaceWith(localVariableMap[path.node.name]); |
|
} |
|
} |
|
}); |
|
var defaultExports = t.objectExpression(Object.keys(namedExports).map(function (name) { |
|
return t.objectProperty(t.identifier(name), namedExports[name]); |
|
})); |
|
moduleExportsByPath[file.filename] = { |
|
namedExports: namedExports, |
|
defaultExports: defaultExports |
|
}; |
|
statements.push(t.variableDeclaration( |
|
'var', |
|
Object.keys(namedExports).map(function (name) { |
|
return t.variableDeclarator(namedExports[name]); |
|
}) |
|
)) |
|
statements.push.apply(statements, file.ast.program.body); |
|
}); |
|
var propertyDefinitions = []; |
|
parsedFiles.forEach(function (file) { |
|
var dashed = camelToDashed(file.property); |
|
propertyDefinitions.push( |
|
t.objectProperty( |
|
t.identifier(file.property), |
|
t.identifier(file.property + '_export_definition') |
|
) |
|
); |
|
if (file.property !== dashed) { |
|
propertyDefinitions.push( |
|
t.objectProperty( |
|
t.stringLiteral(dashed), |
|
t.identifier(file.property + '_export_definition') |
|
) |
|
); |
|
} |
|
}); |
|
var definePropertiesCall = t.callExpression( |
|
t.memberExpression( |
|
t.identifier('Object'), |
|
t.identifier('defineProperties') |
|
), |
|
[ |
|
t.identifier('prototype'), |
|
t.objectExpression( |
|
propertyDefinitions |
|
) |
|
] |
|
); |
|
statements.push(t.expressionStatement( |
|
t.assignmentExpression( |
|
'=', |
|
t.memberExpression( |
|
t.identifier('module'), |
|
t.identifier('exports') |
|
), |
|
t.functionExpression( |
|
null, |
|
[t.identifier('prototype')], |
|
t.blockStatement([t.expressionStatement(definePropertiesCall)]) |
|
) |
|
) |
|
)); |
|
out_file.write(generate(t.program(statements)).code + '\n') |
|
out_file.end(function (err) { |
|
if (err) { |
|
throw err; |
|
} |
|
});
|
|
|