Alex A. Naanou 8e139a80e1 added cli progress reporting...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2020-12-09 06:27:11 +03:00

351 lines
9.7 KiB
JavaScript
Executable File

/**********************************************************************
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var util = require('lib/util')
var actions = require('lib/actions')
var features = require('lib/features')
var data = require('imagegrid/data')
var images = require('imagegrid/images')
var core = require('features/core')
var base = require('features/base')
if(typeof(process) != 'undefined'){
var pathlib = requirejs('path')
var argv = requirejs('lib/argv')
var progress = requirejs('cli-progress')
var colors = requirejs('colors')
}
/*********************************************************************/
// XXX what we need here is:
// - base introspection
// - list features
// - list actions
// - list action scripts / commands
// - call action
// - call action script (a-la git commands)
// - repl (debug/testing)
//
// XXX the main functionality:
// - make previews
// - make index
// - merge
// - clone
//
// XXX a different approach to this would be an "external" cli controller
// script that would contain only cli code and load the ImageGrid
// only in the handler...
// + would be allot faster to load.
// + more flexible as we can load more than one instance...
// This could still be done via features, just load the cli feature
// alone at first and then either create new instances or setup
// additional features as needed...
var CLIActions = actions.Actions({
get cliActions(){
return this.actions
.filter(function(action){
return this.getActionAttr(action, 'cli') }.bind(this)) },
// XXX should this be here???
// ...move this to progress...
// XXX we are missing some beats, is this because we do not let the
// bar update before closing???
__progress: null,
showProgress: ['- System/',
function(text, value, max){
var msg = text instanceof Array ?
text.slice(1).join(': ')
: null
text = text instanceof Array ?
text[0]
: text
var settings = this.__progress = this.__progress || {}
var state = settings[text] = settings[text] || {}
var l = Math.max(text.length, settings.__text_length || 0)
// length changed -> update the bars...
l != settings.__text_length
&& Object.entries(settings)
.forEach(function([key, value]){
value instanceof Object
&& 'bar' in value
&& value.bar.update({text: key.padEnd(l)}) })
settings.__text_length = l
// normalize max and value...
value = state.value =
value != null ?
(typeof(value) == typeof('str') && /[+-][0-9]+/.test(value) ?
(state.value || 0) + parseInt(value)
: value)
: state.value
max = state.max =
max != null ?
(typeof(max) == typeof('str') && /[+-][0-9]+/.test(max) ?
(state.max || 0) + parseInt(max)
: max)
: state.max
var container = settings.__multi_bar =
settings.__multi_bar
|| new progress.MultiBar({
// XXX make this simpler...
format: '{text} {bar} {percentage}% '
+'| ETA: {eta_formatted} | {value}/{total}',
autopadding: true,
},
progress.Presets.rect)
var bar = state.bar =
state.bar || container.create(0, 0, {text: text.padEnd(l)})
bar.setTotal(Math.max(max, value))
bar.update(value)
}],
// handle logger progress...
// XXX this is a copy from ui-progress -- need to reuse...
handleLogItem: ['- System/',
function(logger, path, status, ...rest){
var msg = path.join(': ')
var l = (rest.length == 1 && rest[0] instanceof Array) ?
rest[0].length
: rest.length
// only pass the relevant stuff...
var attrs = {}
logger.ondone
&& (attrs.ondone = logger.ondone)
logger.onclose
&& (attrs.onclose = logger.onclose)
// get keywords...
var {add, done, skip, reset, close, error} =
this.config['progress-logger-keywords']
|| {}
// setup default aliases...
add = new Set([...(add || []), 'added'])
done = new Set([...(done || [])])
skip = new Set([...(skip || []), 'skipped'])
reset = new Set([...(reset || [])])
close = new Set([...(close || []), 'closed'])
error = new Set([...(error || [])])
// close...
if(status == 'close' || close.has(status)){
//this.showProgress(path, 'close')
// reset...
} else if(status == 'reset' || reset.has(status)){
//this.showProgress(path, 'reset')
// added new item -- increase max...
// XXX show msg in the progress bar???
} else if(status == 'add' || add.has(status)){
this.showProgress(path, '+0', '+'+l)
// resolved item -- increase done...
} else if(status == 'done' || done.has(status)){
this.showProgress(path, '+'+l)
// skipped item -- increase done...
// XXX should we instead decrease max here???
// ...if not this is the same as done -- merge...
} else if(status == 'skip' || skip.has(status)){
this.showProgress(path, '+'+l)
// error...
// XXX STUB...
} else if(status == 'error' || error.has(status)){
this.showProgress(['Error'].concat(msg), '+0', '+'+l) }
}],
startREPL: ['- System/Start CLI interpreter',
{cli: '@repl'},
function(){
var repl = nodeRequire('repl')
this._keep_running = true
// setup the global ns...
global.ig =
global.ImageGrid =
this
require('features/all')
global.ImageGridFeatures = core.ImageGridFeatures
//var ig = core.ImageGridFeatures
repl
.start({
prompt: 'ig> ',
useGlobal: true,
input: process.stdin,
output: process.stdout,
//ignoreUndefined: true,
})
.on('exit', function(){
//ig.stop()
process.exit() }) }],
// XXX
startGUI: ['- System/Start viewer GUI',
{cli: '@gui'},
function(){
// XXX
}],
// XXX metadata caching and preview creation are not in sync, can
// this be a problem???
// ...if not, add a note...
// XXX should we support creating multiple indexes at the same time???
// XXX this is reletively generic, might be useful globally...
// XXX add support for cwd and relative paths...
// XXX should we use a clean index or do this in-place???
makeIndex: ['- System/Make index',
{cli: {
name: '@make',
arg: 'PATH',
valueRequired: true,
}},
function(path){
var that = this
path = util.normalizePath(path)
// XXX should we use a clean index or do this in-place???
//var index = this.constructor(..)
var index = this
return index.loadImages(path)
// save base index...
.then(function(){
return index.saveIndex() })
// sharp stuff...
.then(function(){
if(index.makePreviews){
return Promise.all([
// NOTE: this is already running after .loadImages(..)
//index.cacheMetadata('all'),
index.makePreviews('all') ])} })
.then(function(){
return index
.sortImages()
.saveIndex() }) }],
})
// XXX revise architecture....
// XXX move this to the argv parser used in object.js
var CLI =
module.CLI = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'commandline',
depends: [
'lifecycle',
'logger',
],
// XXX should this be ONLY node???
isApplicable: function(){
return this.runtime.node && !this.runtime.browser },
actions: CLIActions,
handlers: [
// supress logging by default...
['start.pre',
function(){
this.logger
&& (this.logger.quiet = true) }],
// handle args...
['ready',
function(){
var that = this
var pkg = nodeRequire('./package.json')
argv.Parser({
// XXX argv.js is not picking these up because
// of the require(..) mixup...
author: pkg.author,
version: pkg.version,
license: pkg.license,
'-verbose': {
doc: 'Enable verbose (very) output',
handler: function(){
that.logger
&& (that.logger.quiet = false) } },
// XXX setup presets...
// ...load sets of features and allow user
// to block/add specific features...
// XXX feature config...
// ...get/set persistent config values...
// build the action command list...
...this.cliActions
.reduce(function(res, action){
var cmd = that.getActionAttr(action, 'cli')
if(typeof(cmd) == typeof('str') || cmd === true){
var name = cmd
var cmd = {name} }
var name = name === true ?
action
: cmd.name
res[name] = {
doc: (that.getActionAttr(action, 'doc') || '')
.split(/[\\\/]/g).pop(),
// XXX revise argument passing...
// ...this must be as flexible as possible...
handler: function(rest, key, value){
return that[action](value) },
...cmd,
}
return res }, {}),
})
.onNoArgs(function(args){
console.log('No args.')
// XXX we should either start the GUI here or print help...
args.push('--help')
//args.push('gui')
})
.then(function(){
// XXX
})()
// XXX is this the right way to trigger state change
// from within a state action...
!this._keep_running
&& this.afterAction(function(){ process.exit() })
}],
],
})
/**********************************************************************
* vim:set ts=4 sw=4 : */
return module })