* Moved oputil.js config import-areas to 'oputil.js mb import-areas'

* 'oputil.js mb import-areas' now *optionally* binds areas to FTN networks. Otherwise only areas are imported
This commit is contained in:
Bryan Ashby 2018-11-24 20:02:19 -07:00
parent a5f3a65faa
commit 2c4fdfdd5f
6 changed files with 407 additions and 353 deletions

View File

@ -216,7 +216,7 @@ function initialize(cb) {
}, },
function loadSysOpInformation(callback) { function loadSysOpInformation(callback) {
// //
// Copy over some +op information from the user DB -> system propertys. // Copy over some +op information from the user DB -> system properties.
// * Makes this accessible for MCI codes, easy non-blocking access, etc. // * Makes this accessible for MCI codes, easy non-blocking access, etc.
// * We do this every time as the op is free to change this information just // * We do this every time as the op is free to change this information just
// like any other user // like any other user

View File

@ -7,6 +7,11 @@ const db = require('../../core/database.js');
const _ = require('lodash'); const _ = require('lodash');
const async = require('async'); const async = require('async');
const inq = require('inquirer');
const fs = require('fs');
const hjson = require('hjson');
const packageJson = require('../../package.json');
exports.printUsageAndSetExitCode = printUsageAndSetExitCode; exports.printUsageAndSetExitCode = printUsageAndSetExitCode;
exports.getDefaultConfigPath = getDefaultConfigPath; exports.getDefaultConfigPath = getDefaultConfigPath;
@ -14,6 +19,17 @@ exports.getConfigPath = getConfigPath;
exports.initConfigAndDatabases = initConfigAndDatabases; exports.initConfigAndDatabases = initConfigAndDatabases;
exports.getAreaAndStorage = getAreaAndStorage; exports.getAreaAndStorage = getAreaAndStorage;
exports.looksLikePattern = looksLikePattern; exports.looksLikePattern = looksLikePattern;
exports.getAnswers = getAnswers;
exports.writeConfig = writeConfig;
const HJSONStringifyCommonOpts = exports.HJSONStringifyCommonOpts = {
emitRootBraces : true,
bracesSameLine : true,
space : 4,
keepWsc : true,
quotes : 'min',
eol : '\n',
};
const exitCodes = exports.ExitCodes = { const exitCodes = exports.ExitCodes = {
SUCCESS : 0, SUCCESS : 0,
@ -101,3 +117,22 @@ function looksLikePattern(tag) {
return /[*?[\]!()+|^]/.test(tag); return /[*?[\]!()+|^]/.test(tag);
} }
function getAnswers(questions, cb) {
inq.prompt(questions).then( answers => {
return cb(answers);
});
}
function writeConfig(config, path) {
config = hjson.stringify(config, HJSONStringifyCommonOpts)
.replace(/%ENIG_VERSION%/g, packageJson.version)
.replace(/%HJSON_VERSION%/g, hjson.version);
try {
fs.writeFileSync(path, config, 'utf8');
return true;
} catch(e) {
return false;
}
}

View File

@ -9,10 +9,11 @@ const {
getConfigPath, getConfigPath,
argv, argv,
ExitCodes, ExitCodes,
initConfigAndDatabases getAnswers,
writeConfig,
HJSONStringifyCommonOpts,
} = require('./oputil_common.js'); } = require('./oputil_common.js');
const getHelpFor = require('./oputil_help.js').getHelpFor; const getHelpFor = require('./oputil_help.js').getHelpFor;
const Errors = require('../../core/enig_error.js').Errors;
// deps // deps
const async = require('async'); const async = require('async');
@ -24,17 +25,8 @@ const paths = require('path');
const _ = require('lodash'); const _ = require('lodash');
const sanatizeFilename = require('sanitize-filename'); const sanatizeFilename = require('sanitize-filename');
const packageJson = require('../../package.json');
exports.handleConfigCommand = handleConfigCommand; exports.handleConfigCommand = handleConfigCommand;
function getAnswers(questions, cb) {
inq.prompt(questions).then( answers => {
return cb(answers);
});
}
const ConfigIncludeKeys = [ const ConfigIncludeKeys = [
'theme', 'theme',
'users.preAuthIdleLogoutSeconds', 'users.idleLogoutSeconds', 'users.preAuthIdleLogoutSeconds', 'users.idleLogoutSeconds',
@ -46,15 +38,6 @@ const ConfigIncludeKeys = [
'logging.rotatingFile', 'logging.rotatingFile',
]; ];
const HJSONStringifyComonOpts = {
emitRootBraces : true,
bracesSameLine : true,
space : 4,
keepWsc : true,
quotes : 'min',
eol : '\n',
};
const QUESTIONS = { const QUESTIONS = {
Intro : [ Intro : [
{ {
@ -231,34 +214,21 @@ function askNewConfigQuestions(cb) {
); );
} }
function writeConfig(config, path) {
config = hjson.stringify(config, HJSONStringifyComonOpts)
.replace(/%ENIG_VERSION%/g, packageJson.version)
.replace(/%HJSON_VERSION%/g, hjson.version)
;
try {
fs.writeFileSync(path, config, 'utf8');
return true;
} catch(e) {
return false;
}
}
const copyFileSyncSilent = (to, from, flags) => { const copyFileSyncSilent = (to, from, flags) => {
try { try {
fs.copyFileSync(to, from, flags); fs.copyFileSync(to, from, flags);
} catch(e) {} } catch(e) {
/* absorb! */
}
}; };
function buildNewConfig() { function buildNewConfig() {
askNewConfigQuestions( (err, configPath, config) => { askNewConfigQuestions( (err, configPath, config) => {
if(err) { if(err) { return;
return;
} }
const bn = sanatizeFilename(config.general.boardName) const bn = sanatizeFilename(config.general.boardName)
.replace(/[^a-z0-9_\-]/ig, '_') .replace(/[^a-z0-9_-]/ig, '_')
.replace(/_+/g, '_') .replace(/_+/g, '_')
.toLowerCase(); .toLowerCase();
const menuFile = `${bn}-menu.hjson`; const menuFile = `${bn}-menu.hjson`;
@ -273,7 +243,7 @@ function buildNewConfig() {
paths.join(__dirname, '../../misc/prompt_template.in.hjson'), paths.join(__dirname, '../../misc/prompt_template.in.hjson'),
paths.join(__dirname, '../../config/', promptFile), paths.join(__dirname, '../../config/', promptFile),
fs.constants.COPYFILE_EXCL fs.constants.COPYFILE_EXCL
) );
config.general.menuFile = menuFile; config.general.menuFile = menuFile;
config.general.promptFile = promptFile; config.general.promptFile = promptFile;
@ -286,294 +256,10 @@ function buildNewConfig() {
}); });
} }
function validateUplinks(uplinks) {
const ftnAddress = require('../../core/ftn_address.js');
const valid = uplinks.every(ul => {
const addr = ftnAddress.fromString(ul);
return addr;
});
return valid;
}
function getMsgAreaImportType(path) {
if(argv.type) {
return argv.type.toLowerCase();
}
const ext = paths.extname(path).toLowerCase().substr(1);
return ext; // .bbs|.na|...
}
function importAreas() {
const importPath = argv._[argv._.length - 1];
if(argv._.length < 3 || !importPath || 0 === importPath.length) {
return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR);
}
const importType = getMsgAreaImportType(importPath);
if('na' !== importType && 'bbs' !== importType) {
return console.error(`"${importType}" is not a recognized import file type`);
}
// optional data - we'll prompt if for anything not found
let confTag = argv.conf;
let networkName = argv.network;
let uplinks = argv.uplinks;
if(uplinks) {
uplinks = uplinks.split(/[\s,]+/);
}
let importEntries;
async.waterfall(
[
function readImportFile(callback) {
fs.readFile(importPath, 'utf8', (err, importData) => {
if(err) {
return callback(err);
}
importEntries = getImportEntries(importType, importData);
if(0 === importEntries.length) {
return callback(Errors.Invalid('Invalid or empty import file'));
}
// We should have enough to validate uplinks
if('bbs' === importType) {
for(let i = 0; i < importEntries.length; ++i) {
if(!validateUplinks(importEntries[i].uplinks)) {
return callback(Errors.Invalid('Invalid uplink(s)'));
}
}
} else {
if(!validateUplinks(uplinks)) {
return callback(Errors.Invalid('Invalid uplink(s)'));
}
}
return callback(null);
});
},
function init(callback) {
return initConfigAndDatabases(callback);
},
function validateAndCollectInput(callback) {
const msgArea = require('../../core/message_area.js');
const sysConfig = require('../../core/config.js').get();
let msgConfs = msgArea.getSortedAvailMessageConferences(null, { noClient : true } );
if(!msgConfs) {
return callback(Errors.DoesNotExist('No conferences exist in your configuration'));
}
msgConfs = msgConfs.map(mc => {
return {
name : mc.conf.name,
value : mc.confTag,
};
});
if(confTag && !msgConfs.find(mc => {
return confTag === mc.value;
}))
{
return callback(Errors.DoesNotExist(`Conference "${confTag}" does not exist`));
}
let existingNetworkNames = [];
if(_.has(sysConfig, 'messageNetworks.ftn.networks')) {
existingNetworkNames = Object.keys(sysConfig.messageNetworks.ftn.networks);
}
if(0 === existingNetworkNames.length) {
return callback(Errors.DoesNotExist('No FTN style networks exist in your configuration'));
}
if(networkName && !existingNetworkNames.find(net => networkName === net)) {
return callback(Errors.DoesNotExist(`FTN style Network "${networkName}" does not exist`));
}
getAnswers([
{
name : 'confTag',
message : 'Message conference:',
type : 'list',
choices : msgConfs,
pageSize : 10,
when : !confTag,
},
{
name : 'networkName',
message : 'Network name:',
type : 'list',
choices : existingNetworkNames,
when : !networkName,
},
{
name : 'uplinks',
message : 'Uplink(s) (comma separated):',
type : 'input',
validate : (input) => {
const inputUplinks = input.split(/[\s,]+/);
return validateUplinks(inputUplinks) ? true : 'Invalid uplink(s)';
},
when : !uplinks && 'bbs' !== importType,
}
],
answers => {
confTag = confTag || answers.confTag;
networkName = networkName || answers.networkName;
uplinks = uplinks || answers.uplinks;
importEntries.forEach(ie => {
ie.areaTag = ie.ftnTag.toLowerCase();
});
return callback(null);
});
},
function confirmWithUser(callback) {
const sysConfig = require('../../core/config.js').get();
console.info(`Importing the following for "${confTag}" - (${sysConfig.messageConferences[confTag].name} - ${sysConfig.messageConferences[confTag].desc})`);
importEntries.forEach(ie => {
console.info(` ${ie.ftnTag} - ${ie.name}`);
});
console.info('');
console.info('Importing will NOT create required FTN network configurations.');
console.info('If you have not yet done this, you will need to complete additional steps after importing.');
console.info('See docs/msg_networks.md for details.');
console.info('');
getAnswers([
{
name : 'proceed',
message : 'Proceed?',
type : 'confirm',
}
],
answers => {
return callback(answers.proceed ? null : Errors.General('User canceled'));
});
},
function loadConfigHjson(callback) {
const configPath = getConfigPath();
fs.readFile(configPath, 'utf8', (err, confData) => {
if(err) {
return callback(err);
}
let config;
try {
config = hjson.parse(confData, { keepWsc : true } );
} catch(e) {
return callback(e);
}
return callback(null, config);
});
},
function performImport(config, callback) {
const confAreas = { messageConferences : {} };
confAreas.messageConferences[confTag] = { areas : {} };
const msgNetworks = { messageNetworks : { ftn : { areas : {} } } };
importEntries.forEach(ie => {
const specificUplinks = ie.uplinks || uplinks; // AREAS.BBS has specific uplinks per area
confAreas.messageConferences[confTag].areas[ie.areaTag] = {
name : ie.name,
desc : ie.name,
};
msgNetworks.messageNetworks.ftn.areas[ie.areaTag] = {
network : networkName,
tag : ie.ftnTag,
uplinks : specificUplinks
};
});
const newConfig = _.defaultsDeep(config, confAreas, msgNetworks);
const configPath = getConfigPath();
if(!writeConfig(newConfig, configPath)) {
return callback(Errors.UnexpectedState('Failed writing configuration'));
}
return callback(null);
}
],
err => {
if(err) {
console.error(err.reason ? err.reason : err.message);
} else {
const addFieldUpd = 'bbs' === importType ? '"name" and "desc"' : '"desc"';
console.info('Configuration generated.');
console.info(`You may wish to validate changes made to ${getConfigPath()}`);
console.info(`as well as update ${addFieldUpd} fields, sorting, etc.`);
console.info('');
}
}
);
}
function getImportEntries(importType, importData) {
let importEntries = [];
if('na' === importType) {
//
// parse out
// TAG DESC
//
const re = /^([^\s]+)\s+([^\r\n]+)/gm;
let m;
while( (m = re.exec(importData) )) {
importEntries.push({
ftnTag : m[1],
name : m[2],
});
}
} else if ('bbs' === importType) {
//
// Various formats for AREAS.BBS seem to exist. We want to support as much as possible.
//
// SBBS http://www.synchro.net/docs/sbbsecho.html#AREAS.BBS
// CODE TAG UPLINKS
//
// VADV https://www.vadvbbs.com/products/vadv/support/docs/docs_vfido.php#AREAS.BBS
// TAG UPLINKS
//
// Misc
// PATH|OTHER TAG UPLINKS
//
// Assume the second item is TAG and 1:n UPLINKS (space and/or comma sep) after (at the end)
//
const re = /^[^\s]+\s+([^\s]+)\s+([^\n]+)$/gm;
let m;
while ( (m = re.exec(importData) )) {
const tag = m[1];
importEntries.push({
ftnTag : tag,
name : `Area: ${tag}`,
uplinks : m[2].split(/[\s,]+/),
});
}
}
return importEntries;
}
function catCurrentConfig() { function catCurrentConfig() {
try { try {
const config = hjson.rt.parse(fs.readFileSync(getConfigPath(), 'utf8')); const config = hjson.rt.parse(fs.readFileSync(getConfigPath(), 'utf8'));
const hjsonOpts = Object.assign({}, HJSONStringifyComonOpts, { const hjsonOpts = Object.assign({}, HJSONStringifyCommonOpts, {
colors : false === argv.colors ? false : true, colors : false === argv.colors ? false : true,
keepWsc : false === argv.comments ? false : true, keepWsc : false === argv.comments ? false : true,
}); });
@ -597,7 +283,6 @@ function handleConfigCommand() {
switch(action) { switch(action) {
case 'new' : return buildNewConfig(); case 'new' : return buildNewConfig();
case 'import-areas' : return importAreas();
case 'cat' : return catCurrentConfig(); case 'cat' : return catCurrentConfig();
default : return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR); default : return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR);

View File

@ -39,15 +39,8 @@ actions:
actions: actions:
new generate a new/initial configuration new generate a new/initial configuration
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH
cat cat current configuration to stdout cat cat current configuration to stdout
import-areas args:
--conf CONF_TAG specify conference tag in which to import areas
--network NETWORK specify network name/key to associate FTN areas
--uplinks UL1,UL2,... specify one or more comma separated uplinks
--type TYPE specifies area import type. valid options are "bbs" and "na"
cat args: cat args:
--no-color disable color --no-color disable color
--no-comments strip any comments --no-comments strip any comments
@ -105,6 +98,13 @@ general information:
areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s) areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s)
one or more commands may be supplied. commands that are multi one or more commands may be supplied. commands that are multi
part such as "%COMPRESS ZIP" should be quoted. part such as "%COMPRESS ZIP" should be quoted.
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH
import-areas args:
--conf CONF_TAG conference tag in which to import areas
--network NETWORK network name/key to associate FTN areas
--uplinks UL1,UL2,... one or more comma separated uplinks
--type TYPE area import type. valid options are "bbs" and "na"
` `
}; };

View File

@ -2,16 +2,25 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
'use strict'; 'use strict';
const printUsageAndSetExitCode = require('./oputil_common.js').printUsageAndSetExitCode; const {
const ExitCodes = require('./oputil_common.js').ExitCodes; printUsageAndSetExitCode,
const argv = require('./oputil_common.js').argv; getConfigPath,
const initConfigAndDatabases = require('./oputil_common.js').initConfigAndDatabases; ExitCodes,
argv,
initConfigAndDatabases,
getAnswers,
writeConfig,
} = require('./oputil_common.js');
const getHelpFor = require('./oputil_help.js').getHelpFor; const getHelpFor = require('./oputil_help.js').getHelpFor;
const Address = require('../ftn_address.js'); const Address = require('../ftn_address.js');
const Errors = require('../enig_error.js').Errors; const Errors = require('../enig_error.js').Errors;
// deps // deps
const async = require('async'); const async = require('async');
const paths = require('path');
const fs = require('fs');
const hjson = require('hjson');
const _ = require('lodash');
exports.handleMessageBaseCommand = handleMessageBaseCommand; exports.handleMessageBaseCommand = handleMessageBaseCommand;
@ -121,6 +130,310 @@ function areaFix() {
); );
} }
function validateUplinks(uplinks) {
const ftnAddress = require('../../core/ftn_address.js');
const valid = uplinks.every(ul => {
const addr = ftnAddress.fromString(ul);
return addr;
});
return valid;
}
function getMsgAreaImportType(path) {
if(argv.type) {
return argv.type.toLowerCase();
}
return paths.extname(path).substr(1).toLowerCase(); // bbs|na|...
}
function importAreas() {
const importPath = argv._[argv._.length - 1];
if(argv._.length < 3 || !importPath || 0 === importPath.length) {
return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR);
}
const importType = getMsgAreaImportType(importPath);
if('na' !== importType && 'bbs' !== importType) {
return console.error(`"${importType}" is not a recognized import file type`);
}
// optional data - we'll prompt if for anything not found
let confTag = argv.conf;
let networkName = argv.network;
let uplinks = argv.uplinks;
if(uplinks) {
uplinks = uplinks.split(/[\s,]+/);
}
let importEntries;
async.waterfall(
[
function readImportFile(callback) {
fs.readFile(importPath, 'utf8', (err, importData) => {
if(err) {
return callback(err);
}
importEntries = getImportEntries(importType, importData);
if(0 === importEntries.length) {
return callback(Errors.Invalid('Invalid or empty import file'));
}
// We should have enough to validate uplinks
if('bbs' === importType) {
for(let i = 0; i < importEntries.length; ++i) {
if(!validateUplinks(importEntries[i].uplinks)) {
return callback(Errors.Invalid('Invalid uplink(s)'));
}
}
} else {
if(!validateUplinks(uplinks || [])) {
return callback(Errors.Invalid('Invalid uplink(s)'));
}
}
return callback(null);
});
},
function init(callback) {
return initConfigAndDatabases(callback);
},
function validateAndCollectInput(callback) {
const msgArea = require('../../core/message_area.js');
const sysConfig = require('../../core/config.js').get();
let msgConfs = msgArea.getSortedAvailMessageConferences(null, { noClient : true } );
if(!msgConfs) {
return callback(Errors.DoesNotExist('No conferences exist in your configuration'));
}
msgConfs = msgConfs.map(mc => {
return {
name : mc.conf.name,
value : mc.confTag,
};
});
if(confTag && !msgConfs.find(mc => {
return confTag === mc.value;
}))
{
return callback(Errors.DoesNotExist(`Conference "${confTag}" does not exist`));
}
const existingNetworkNames = Object.keys(_.get(sysConfig, 'messageNetworks.ftn.networks', {}));
if(networkName && !existingNetworkNames.find(net => networkName === net)) {
return callback(Errors.DoesNotExist(`FTN style Network "${networkName}" does not exist`));
}
// can't use --uplinks without a network
if(!networkName && 0 === existingNetworkNames.length && uplinks) {
return callback(Errors.Invalid('Cannot use --uplinks without an FTN network to import to'));
}
getAnswers([
{
name : 'confTag',
message : 'Message conference:',
type : 'list',
choices : msgConfs,
pageSize : 10,
when : !confTag,
},
{
name : 'networkName',
message : 'FTN network name:',
type : 'list',
choices : [ '-None-' ].concat(existingNetworkNames),
pageSize : 10,
when : !networkName && existingNetworkNames.length > 0,
filter : (choice) => {
return '-None-' === choice ? undefined : choice;
}
},
],
answers => {
confTag = confTag || answers.confTag;
networkName = networkName || answers.networkName;
uplinks = uplinks || answers.uplinks;
importEntries.forEach(ie => {
ie.areaTag = ie.ftnTag.toLowerCase();
});
return callback(null);
});
},
function collectUplinks(callback) {
if(!networkName || uplinks || 'bbs' === importType) {
return callback(null);
}
getAnswers([
{
name : 'uplinks',
message : 'Uplink(s) (comma separated):',
type : 'input',
validate : (input) => {
const inputUplinks = input.split(/[\s,]+/);
return validateUplinks(inputUplinks) ? true : 'Invalid uplink(s)';
},
}
],
answers => {
uplinks = answers.uplinks;
return callback(null);
});
},
function confirmWithUser(callback) {
const sysConfig = require('../../core/config.js').get();
console.info(`Importing the following for "${confTag}"`);
console.info(`(${sysConfig.messageConferences[confTag].name} - ${sysConfig.messageConferences[confTag].desc})`);
console.info('');
importEntries.forEach(ie => {
console.info(` ${ie.ftnTag} - ${ie.name}`);
});
if(networkName) {
console.info('');
console.info(`For FTN network: ${networkName}`);
console.info(`Uplinks: ${uplinks}`);
console.info('');
console.info('Importing will NOT create required FTN network configurations.');
console.info('If you have not yet done this, you will need to complete additional steps after importing.');
console.info('See Message Networks docs for details.');
console.info('');
}
getAnswers([
{
name : 'proceed',
message : 'Proceed?',
type : 'confirm',
}
],
answers => {
return callback(answers.proceed ? null : Errors.General('User canceled'));
});
},
function loadConfigHjson(callback) {
const configPath = getConfigPath();
fs.readFile(configPath, 'utf8', (err, confData) => {
if(err) {
return callback(err);
}
let config;
try {
config = hjson.parse(confData, { keepWsc : true } );
} catch(e) {
return callback(e);
}
return callback(null, config);
});
},
function performImport(config, callback) {
const confAreas = { messageConferences : {} };
confAreas.messageConferences[confTag] = { areas : {} };
const msgNetworks = { messageNetworks : { ftn : { areas : {} } } };
importEntries.forEach(ie => {
const specificUplinks = ie.uplinks || uplinks; // AREAS.BBS has specific uplinks per area
confAreas.messageConferences[confTag].areas[ie.areaTag] = {
name : ie.name,
desc : ie.name,
};
if(networkName) {
msgNetworks.messageNetworks.ftn.areas[ie.areaTag] = {
network : networkName,
tag : ie.ftnTag,
uplinks : specificUplinks
};
}
});
const newConfig = _.defaultsDeep(config, confAreas, msgNetworks);
const configPath = getConfigPath();
if(!writeConfig(newConfig, configPath)) {
return callback(Errors.UnexpectedState('Failed writing configuration'));
}
return callback(null);
}
],
err => {
if(err) {
console.error(err.reason ? err.reason : err.message);
} else {
const addFieldUpd = 'bbs' === importType ? '"name" and "desc"' : '"desc"';
console.info('Configuration generated.');
console.info(`You may wish to validate changes made to ${getConfigPath()}`);
console.info(`as well as update ${addFieldUpd} fields, sorting, etc.`);
console.info('');
}
}
);
}
function getImportEntries(importType, importData) {
let importEntries = [];
if('na' === importType) {
//
// parse out
// TAG DESC
//
const re = /^([^\s]+)\s+([^\r\n]+)/gm;
let m;
while( (m = re.exec(importData) )) {
importEntries.push({
ftnTag : m[1].trim(),
name : m[2].trim(),
});
}
} else if ('bbs' === importType) {
//
// Various formats for AREAS.BBS seem to exist. We want to support as much as possible.
//
// SBBS http://www.synchro.net/docs/sbbsecho.html#AREAS.BBS
// CODE TAG UPLINKS
//
// VADV https://www.vadvbbs.com/products/vadv/support/docs/docs_vfido.php#AREAS.BBS
// TAG UPLINKS
//
// Misc
// PATH|OTHER TAG UPLINKS
//
// Assume the second item is TAG and 1:n UPLINKS (space and/or comma sep) after (at the end)
//
const re = /^[^\s]+\s+([^\s]+)\s+([^\n]+)$/gm;
let m;
while ( (m = re.exec(importData) )) {
const tag = m[1].trim();
importEntries.push({
ftnTag : tag,
name : `Area: ${tag}`,
uplinks : m[2].trim().split(/[\s,]+/),
});
}
}
return importEntries;
}
function handleMessageBaseCommand() { function handleMessageBaseCommand() {
function errUsage() { function errUsage() {
@ -138,5 +451,6 @@ function handleMessageBaseCommand() {
return({ return({
areafix : areaFix, areafix : areaFix,
'import-areas' : importAreas,
}[action] || errUsage)(); }[action] || errUsage)();
} }

View File

@ -71,22 +71,17 @@ usage: optutil.js config <action> [<args>]
actions: actions:
new generate a new/initial configuration new generate a new/initial configuration
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH cat cat current configuration to stdout
import-areas args: cat args:
--conf CONF_TAG specify conference tag in which to import areas --no-color disable color
--network NETWORK specify network name/key to associate FTN areas --no-comments strip any comments
--uplinks UL1,UL2,... specify one or more comma separated uplinks
--type TYPE specifies area import type. valid options are "bbs" and "na"
``` ```
| Action | Description | Examples | | Action | Description | Examples |
|-----------|-------------------|---------------------------------------| |-----------|-------------------|---------------------------------------|
| `new` | Generates a new/initial configuration | `./oputil.js config new` (follow the prompts) | | `new` | Generates a new/initial configuration | `./oputil.js config new` (follow the prompts) |
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file | `./oputil.js config import-areas /some/path/l33tnet.na` | | `cat` | Pretty prints current `config.hjson` configuration to stdout. | `./oputil.js config cat` |
When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args".
## File Base Management ## File Base Management
The `fb` command provides a powerful file base management interface. The `fb` command provides a powerful file base management interface.
@ -189,3 +184,28 @@ file_crc32: fc6655d
file_md5: 3455f74bbbf9539e69bd38f45e039a4e file_md5: 3455f74bbbf9539e69bd38f45e039a4e
file_sha1: 558fab3b49a8ac302486e023a3c2a86bd4e4b948 file_sha1: 558fab3b49a8ac302486e023a3c2a86bd4e4b948
``` ```
## Message Base Management
The `mb` command provides various Message Base related tools:
```
usage: oputil.js mb <action> [<args>]
actions:
areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s)
one or more commands may be supplied. commands that are multi
part such as "%COMPRESS ZIP" should be quoted.
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH
import-areas args:
--conf CONF_TAG conference tag in which to import areas
--network NETWORK network name/key to associate FTN areas
--uplinks UL1,UL2,... one or more comma separated uplinks
--type TYPE area import type. valid options are "bbs" and "na"
```
| Action | Description | Examples |
|-----------|-------------------|---------------------------------------|
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js config import-areas /some/path/l33tnet.na` |
When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args".