mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-31 11:20:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			952 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			952 lines
		
	
	
		
			25 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 object = require('lib/object')
 | |
| 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')
 | |
| 
 | |
| 
 | |
| //require('features/all')
 | |
| 
 | |
| 
 | |
| if(typeof(process) != 'undefined'){
 | |
| 	var pathlib = requirejs('path')
 | |
| 	var argv = requirejs('lib/argv')
 | |
| 	var progress = requirejs('cli-progress')
 | |
| 	var colors = requirejs('colors') 
 | |
| 
 | |
| 	var file = require('imagegrid/file') }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*********************************************************************/
 | |
| 
 | |
| var CLIActions = actions.Actions({
 | |
| 	config: {
 | |
| 		// XXX do we care that something is not "ready" here???
 | |
| 		'declare-ready-timeout': 0,
 | |
| 
 | |
| 		'progress-done-delay': 1000,
 | |
| 
 | |
| 		banner: '$APPNAME $VERSION:',
 | |
| 	},
 | |
| 
 | |
| 
 | |
| 	// docs...
 | |
| 	//
 | |
| 	// XXX do a better set of examples...
 | |
| 	cliExamples: [[
 | |
| 		'Create/init index in current directory',
 | |
| 		'$ $SCRIPTNAME init',
 | |
| 		'',
 | |
| 		'Export 500px previews from current index to ./preview directory',
 | |
| 		'$ $SCRIPTNAME export from=. to=./previews --image-size=500',
 | |
| 	]],
 | |
| 
 | |
| 
 | |
| 	// the argvparser...
 | |
| 	//
 | |
| 	// this is set by argv's Parser on .onArgs(..) in .ready(..) handler below...
 | |
| 	argv: undefined,
 | |
| 
 | |
| 	help: ['- System/Show action help',
 | |
| 		function(...actions){
 | |
| 			Object.entries(this.getDoc(actions))
 | |
| 				.forEach(function([action, [s, l]]){
 | |
| 					console.log(l)
 | |
| 					console.log('')
 | |
| 				}) }],
 | |
| 
 | |
| 	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???
 | |
| 	// XXX need to reset this when done...
 | |
| 	__progress: null,
 | |
| 	showProgress: ['- System/',
 | |
| 		function(text, value, max){
 | |
| 			// progress display is disabled...
 | |
| 			if(this.__progress === false){
 | |
| 				return }
 | |
| 
 | |
| 			var msg = text instanceof Array ? 
 | |
| 				text.slice(1).join(': ') 
 | |
| 				: null
 | |
| 			text = text instanceof Array ? 
 | |
| 				text[0] 
 | |
| 				: text
 | |
| 
 | |
| 			var settings = this.__progress = this.__progress ?? {}
 | |
| 			var bars = settings.bars = settings.bars ?? {}
 | |
| 			var state = bars[text] = bars[text] ?? {}
 | |
| 
 | |
| 			if(state.timeout){
 | |
| 				clearTimeout(state.timeout)
 | |
| 				delete state.timeout }
 | |
| 
 | |
| 			// actions...
 | |
| 			if(value == 'reset'){
 | |
| 				// XXX this is not the same as ui-progress...
 | |
| 				// 		...here we first set timeout then and close, 
 | |
| 				// 		there we set to 0 and timeout and close...
 | |
| 				state.timeout = setTimeout(
 | |
| 					function(){
 | |
| 						//this.showProgress(text, 0, 0) }.bind(this),
 | |
| 						this.showProgress(text, 'close') }.bind(this),
 | |
| 					this.config['progress-done-delay'] || 1000)
 | |
| 				return }
 | |
| 			if(value == 'close'){
 | |
| 				delete bars[text]
 | |
| 				// check if no bars left...
 | |
| 				if(Object.keys(bars) == 0){
 | |
| 					delete this.__progress }
 | |
| 				return }
 | |
| 
 | |
| 			var l = Math.max(text.length, settings.__text_length || 0)
 | |
| 			// length changed -> update the bars...
 | |
| 			l != settings.__text_length
 | |
| 				&& Object.entries(bars)
 | |
| 					.forEach(function([key, value]){
 | |
| 						value.bar
 | |
| 							&& 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,
 | |
| 								stopOnComplete: true,
 | |
| 								forceRedraw: true,
 | |
| 							},
 | |
| 							progress.Presets.rect)
 | |
| 						// prepare for printing stuff...
 | |
| 						.run(function(){
 | |
| 							this.on('redraw-pre', function(){
 | |
| 								// XXX need to clear the line -- need to get term-width....
 | |
| 								// XXX this requires a full draw (forceRedraw: true)...
 | |
| 								//console.log('moo'.padEnd(process.stdout.columns))
 | |
| 							}) }))
 | |
| 			var bar = state.bar = 
 | |
| 				state.bar 
 | |
| 					|| container.create(0, 0, {text: text.padEnd(l)})
 | |
| 
 | |
| 			// XXX for some reason this does not work under electron...
 | |
| 			bar.setTotal(Math.max(max, value))
 | |
| 			bar.update(value) 
 | |
| 
 | |
| 			// auto-clear when complete...
 | |
| 			if(value >= max){
 | |
| 				state.timeout = setTimeout(
 | |
| 					function(){
 | |
| 						this.showProgress(text, 'close') }.bind(this), 
 | |
| 					this.config['progress-done-delay'] || 1000) } }],
 | |
| 
 | |
| 	// handle logger progress...
 | |
| 	// XXX reset is called at odd spots by the queue handler (see: features/core.js)
 | |
| 	// XXX this is a copy from ui-progress -- need to reuse if possible...
 | |
| 	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...
 | |
| 			// XXX this seems to be called before "Cache image metadata" is done
 | |
| 			// 		when called from .cliInitIndex(..) -- messing up the numbers...
 | |
| 			} 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) }
 | |
| 		}],
 | |
| 
 | |
| 	// XXX SETUP revise default...
 | |
| 	setupFeatures: ['- System/',
 | |
| 		core.doc`Load required features.
 | |
| 			
 | |
| 			NOTE: this is hete because cli is designed to be loaded in a very
 | |
| 				limited context and for some actions will need additional
 | |
| 				features.
 | |
| 			`,
 | |
| 		function(...tags){
 | |
| 			var features = this.features.FeatureSet
 | |
| 			requirejs('features/all')
 | |
| 			features.setup(this, [
 | |
| 				'imagegrid-testing', 
 | |
| 				...(tags.length == 0 ?
 | |
| 					this.features.input
 | |
| 					: tags),
 | |
| 			]) }],
 | |
| 	setupGlobals: ['- System/',
 | |
| 		function(){
 | |
| 			// setup the global ns...
 | |
| 			global.ig =
 | |
| 			global.ImageGrid = 
 | |
| 				this
 | |
| 			global.help = function(...actions){
 | |
| 				global.ig.help(...actions) }
 | |
| 			global.ImageGridFeatures = core.ImageGridFeatures }],
 | |
| 
 | |
| 
 | |
| 	// basic code runner...
 | |
| 	cliDo: ['- System/CLI/run CODE', 
 | |
| 		{cli: {
 | |
| 			name: '@do',
 | |
| 			arg: 'CODE',
 | |
| 		}},
 | |
| 		function(code){
 | |
| 			var AsyncFunction = (async function(){}).constructor
 | |
| 
 | |
| 			this.setupFeatures()
 | |
| 			this.setupGlobals()
 | |
| 
 | |
| 			AsyncFunction(code)()
 | |
| 
 | |
| 			this.stop() }],
 | |
| 
 | |
| 	// Interactive commands...
 | |
| 	//
 | |
| 	cliStartREPL: ['- System/CLI/start CLI interpreter',
 | |
| 		{cli: {
 | |
| 			name: '@repl',
 | |
| 			arg: 'PATH'
 | |
| 			//interactive: true,
 | |
| 		}},
 | |
| 		function(path, options){
 | |
| 			var that = this
 | |
| 			var package = nodeRequire('./package.json')
 | |
| 
 | |
| 			// XXX SETUP
 | |
| 			this.setupFeatures()
 | |
| 
 | |
| 			if(path){
 | |
| 				this.loadIndex(path) }
 | |
| 
 | |
| 			this.__keep_running = true
 | |
| 
 | |
| 			this.setupGlobals()
 | |
| 
 | |
| 			// start non-tty / script mode...
 | |
| 			if(!process.stdin.isTTY){
 | |
| 				var fs = nodeRequire('fs')
 | |
| 				var AsyncFunction = (async function(){}).constructor
 | |
| 
 | |
| 				AsyncFunction(
 | |
| 					fs.readFileSync(process.stdin.fd, 'utf-8'))()
 | |
| 				this.stop()
 | |
| 
 | |
| 			// start repl mode...
 | |
| 			} else {
 | |
| 				var repl = nodeRequire('repl')
 | |
| 				// print banner...
 | |
| 				var banner = this.banner 
 | |
| 					|| this.config.banner
 | |
| 				banner
 | |
| 					&& process.stdin.isTTY
 | |
| 					&& process.stdout.isTTY
 | |
| 					&& console.log(banner 
 | |
| 						.replace(/\$APPNAME/g, package.name)
 | |
| 						.replace(/\$AUTHOR/g, package.author)
 | |
| 						.replace(/\$REPO/g, package.repository)
 | |
| 						.replace(/\$SCRIPTNAME/g, this.argv.scriptName)
 | |
| 						.replace(/\$VERSION/g, this.version))
 | |
| 
 | |
| 				// start the repl...
 | |
| 				repl
 | |
| 					.start({
 | |
| 						prompt: 'ig> ',
 | |
| 						useGlobal: true,
 | |
| 						input: process.stdin,
 | |
| 						output: process.stdout,
 | |
| 					})
 | |
| 					.on('exit', function(){
 | |
| 						that.stop() }) } }],
 | |
| 	// XXX move this to a feature that requires electron...
 | |
| 	// 		...and move electron to an optional dependency...
 | |
| 	cliStartGUI: ['- System/CLI/start viewer GUI',
 | |
| 		core.doc`
 | |
| 
 | |
| 		NOTE: this will not wait for the viewer to exit.`,
 | |
| 		{cli: argv && argv.Parser({
 | |
| 			key: '@gui',
 | |
| 			arg: 'PATH',
 | |
| 			doc: 'start viewer GUI',
 | |
| 
 | |
| 			'-version': undefined,
 | |
| 			'-quiet': undefined,
 | |
| 
 | |
| 			'-devtools': {
 | |
| 				doc: 'show DevTools',
 | |
| 				type: 'bool',
 | |
| 			},
 | |
| 			'-show': {
 | |
| 				doc: 'force show interface',
 | |
| 				type: 'bool',
 | |
| 			},
 | |
| 		})},
 | |
| 		function(path, options={}){
 | |
| 			var env = { ...process.env }
 | |
| 			path
 | |
| 				&& (env.IMAGEGRID_PATH = 
 | |
| 					util.normalizePath(
 | |
| 						pathlib.resolve(process.cwd(), path)))
 | |
| 			options.devtools
 | |
| 				&& (env.IMAGEGRID_DEBUG = true)
 | |
| 			options.show
 | |
| 				&& (env.IMAGEGRID_FORCE_SHOW = true)
 | |
| 
 | |
| 			// already in electron...
 | |
| 			if(process.versions.electron){
 | |
| 				// XXX this feels hackish... 
 | |
| 				global.START_GUI = true
 | |
| 
 | |
| 			// launch gui...
 | |
| 			} else {
 | |
| 				requirejs('child_process')
 | |
| 					.spawn(requirejs('electron'),
 | |
| 						[ pathlib.join(
 | |
| 							pathlib.dirname(nodeRequire.main.filename), 
 | |
| 							'e.js') ],
 | |
| 						{ 
 | |
| 							detached: true, 
 | |
| 							env,
 | |
| 						}) } }],
 | |
| 
 | |
| 	// XXX
 | |
| 	cliGID: ['- System/GLI/generate GID',
 | |
| 		{cli: {
 | |
| 			name: '@gid',
 | |
| 			arg: 'IMAGE',
 | |
| 			valueRequired: true,
 | |
| 
 | |
| 			// XXX REMOVE WHEN DONE...
 | |
| 			doc: false,
 | |
| 		}},
 | |
| 		function(path){
 | |
| 			// XXX
 | |
| 			console.warn('Not implemented yet...')
 | |
| 		}],
 | |
| 	cliListIndexes: ['- System/CLI/list indexes in PATH',
 | |
| 		{cli: argv && argv.Parser({
 | |
| 			key: '@ls', 
 | |
| 			arg: 'PATH',
 | |
| 			doc: 'list indexes in PATH',
 | |
| 
 | |
| 			'-version': undefined,
 | |
| 			'-quiet': undefined,
 | |
| 
 | |
| 			'-r': '-recursive',
 | |
| 			'-recursive': {
 | |
| 				doc: 'list nested/recursive indexes',
 | |
| 				type: 'bool',
 | |
| 			},
 | |
| 
 | |
| 			'-n': '-nested-only',
 | |
| 			'-nested-only': {
 | |
| 				doc: 'ignore the top-level index and only list the indexes below',
 | |
| 				type: 'bool',
 | |
| 			},
 | |
| 
 | |
| 		})},
 | |
| 		function(path, options={}){
 | |
| 			var that = this
 | |
| 			path = path ?? '.'
 | |
| 			// needed to get the default index dir name...
 | |
| 			this.setupFeatures('fs')
 | |
| 			//this.setupFeatures()
 | |
| 			file.listIndexes(path)
 | |
| 				.on('end', function(paths){
 | |
| 					paths = paths
 | |
| 						.map(function(p){
 | |
| 							return p
 | |
| 								.split(that.config['index-dir'])
 | |
| 								.shift() })
 | |
| 					// normalize path...
 | |
| 					path.at(-1) != '/'
 | |
| 						&& (path += '/')
 | |
| 					// handle --nested-only
 | |
| 					options['nested-only']
 | |
| 						&& paths.includes(path)
 | |
| 						&& paths.splice(paths.indexOf(path), 1)
 | |
| 					paths = options.recursive ? 
 | |
| 						paths
 | |
| 						: file.skipNested(paths)
 | |
| 							.sortAs(paths)
 | |
| 					for(var p of paths){
 | |
| 						console.log(p) } }) }],
 | |
| 
 | |
| 	// 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 should we use a clean index or do this in-place???
 | |
| 	// XXX add ability to disable sort...
 | |
| 	cliInitIndex: ['- System/CLI/make index',
 | |
| 		core.doc`
 | |
| 
 | |
| 			Create index in current directory
 | |
| 			.cliInitIndex()
 | |
| 			.cliInitIndex('create')
 | |
| 				-> promise
 | |
| 
 | |
| 			Create index in path...
 | |
| 			,cliInitIndex(path)
 | |
| 			.cliInitIndex('create', path)
 | |
| 				-> promise
 | |
| 
 | |
| 
 | |
| 			Update index in current directory
 | |
| 			.cliInitIndex('update')
 | |
| 				-> promise
 | |
| 
 | |
| 			Update index in path...
 | |
| 			.cliInitIndex('update', path)
 | |
| 				-> promise
 | |
| 
 | |
| 		`,
 | |
| 		{cli: {
 | |
| 			name: '@init',
 | |
| 			arg: 'PATH',
 | |
| 			//valueRequired: true,
 | |
| 		}},
 | |
| 		function(path, options){
 | |
| 			// XXX SETUP
 | |
| 			this.setupFeatures()
 | |
| 
 | |
| 			// get mode...
 | |
| 			if(path == 'create' || path == 'update'){
 | |
| 				var [mode, path, options] = arguments }
 | |
| 			mode = mode || 'create'
 | |
| 			// normalize path...
 | |
| 			path = util.normalizePath(
 | |
| 				path ?
 | |
| 					pathlib.resolve(process.cwd(), path)
 | |
| 					: process.cwd())
 | |
| 			options = options || {}
 | |
| 
 | |
| 			// XXX should we use a clean index or do this in-place???
 | |
| 			//var index = this.constructor(..)
 | |
| 			var index = this
 | |
| 			return (mode == 'create' ?
 | |
| 					index.loadImages(path)
 | |
| 					: index.loadNewImages(path))
 | |
| 				// save base index...
 | |
| 				.then(function(){ 
 | |
| 					return index.saveIndex() })
 | |
| 				// sharp stuff...
 | |
| 				.then(function(){
 | |
| 					if(index.makePreviews){
 | |
| 						return Promise.all([
 | |
| 							// NOTE: no need to call .cacheMetadata(..) as 
 | |
| 							// 		it is already running after .loadImages(..)
 | |
| 							index.makePreviews('all') ])} })
 | |
| 				.then(function(){
 | |
| 					return index
 | |
| 						.sortImages()
 | |
| 						.saveIndex() }) }],
 | |
| 	// XXX does not work yet...
 | |
| 	cliUpdateIndex: ['- System/CLI/update index',
 | |
| 		{cli: {
 | |
| 			name: '@update',
 | |
| 			arg: 'PATH',
 | |
| 		}},
 | |
| 		'cliInitIndex: "update" ...'],
 | |
| 
 | |
| 	// XXX handle errors...
 | |
| 	cliInfo: ['- System/CLI/show information about index in PATH',
 | |
| 		{cli: {
 | |
| 			name: '@info', 
 | |
| 			arg: 'PATH',
 | |
| 		}},
 | |
| 		function(path, options={}){
 | |
| 			var that = this
 | |
| 			path = path ?? '.'
 | |
| 			this.setupFeatures()
 | |
| 			return this.loadIndex(path)
 | |
| 				.then(
 | |
| 					async function(){
 | |
| 						var modified = 
 | |
| 							Object.values(
 | |
| 								await that.loadSaveHistoryList())
 | |
| 							.map(function(log){
 | |
| 								return Object.keys(log) })
 | |
| 							.flat()
 | |
| 							.sort()
 | |
| 							.pop()
 | |
| 						// calculate core.doc compatible offset for nested items.
 | |
| 						var offset = '\t'.repeat(`
 | |
| 							`.split('\t').length)
 | |
| 						console.log(core.doc`
 | |
| 							Load path: ${ path }
 | |
| 							Index path: ${ that.location.path }
 | |
| 							Loaded indexes: ${ 
 | |
| 								['', ...that.location.loaded].join('\n'+offset) }
 | |
| 							Current image: ${ that.current }
 | |
| 							Image count: ${ that.data.order.length }
 | |
| 							Collections: ${ 
 | |
| 								that.collections ?
 | |
| 									['', ...Object.keys(that.collections || [])].join('\n'+offset)
 | |
| 									: '-' }
 | |
| 							Modified date: ${ modified }`) },
 | |
| 					function(err){
 | |
| 						console.error('Can\'t find or load index at:', path) }) }],
 | |
| 	cliListCollections: ['- System/CLI/list collections in index',
 | |
| 		{cli: argv && argv.Parser({
 | |
| 			key: '@collections',
 | |
| 			doc: 'list collection in index at PATH',
 | |
| 			arg: 'PATH',
 | |
| 
 | |
| 			'-version': undefined,
 | |
| 			'-quiet': undefined,
 | |
| 
 | |
| 			'-f': '-full',
 | |
| 			'-full': {
 | |
| 				doc: 'show full collection information',
 | |
| 				type: 'bool',
 | |
| 			},
 | |
| 		})},
 | |
| 		function(path, options={}){
 | |
| 			var that = this
 | |
| 
 | |
| 			this.setupFeatures()
 | |
| 
 | |
| 			path = path || options.value
 | |
| 			path = util.normalizePath(
 | |
| 				path ?
 | |
| 					pathlib.resolve(process.cwd(), path)
 | |
| 					: process.cwd())
 | |
| 			return this.loadIndex(path)
 | |
| 				.then(
 | |
| 					function(){
 | |
| 						for(var name of that.collection_order || []){
 | |
| 							// XXX revise output formatting...
 | |
| 							options.full ?
 | |
| 					   			console.log(that.collections[name].gid, name) 
 | |
| 								: console.log(name) } },
 | |
| 					function(err){
 | |
| 						// XXX how do we handle rejection???
 | |
| 						console.error('Can\'t find or load index at:', path) }) }],
 | |
| 
 | |
| 	// XXX
 | |
| 	cliCloneIndex: ['- System/CLI/clone index',
 | |
| 		function(){
 | |
| 		}],
 | |
| 	// XXX report that can't find an index...
 | |
| 	// XXX move options to generic object for re-use...
 | |
| 	// XXX how do we handle errors???
 | |
| 	cliExportImages: ['- System/CLI/export images',
 | |
| 		{cli: argv && argv.Parser({
 | |
| 			key: '@export',
 | |
| 			doc: 'export images',
 | |
| 			// help...
 | |
| 			'-help-pattern': {
 | |
| 				doc: 'show image filename pattern info and exit',
 | |
| 				priority: 89,
 | |
| 				handler: function(){
 | |
| 					this.parent.context
 | |
| 						// XXX SETUP
 | |
| 						//.setupFeatures('fs', 'commandline')
 | |
| 						.setupFeatures()
 | |
| 						.help('formatImageName')
 | |
| 					return argv.STOP } },
 | |
| 			'-version': undefined,
 | |
| 			'-quiet': undefined,
 | |
| 			// commands...
 | |
| 			'@from': {
 | |
| 				doc: 'source path',
 | |
| 				arg: 'PATH | from',
 | |
| 				default: '.',
 | |
| 				valueRequired: true, },
 | |
| 			// XXX
 | |
| 			'@collection': {
 | |
| 				doc: 'source collection (name/gid)',
 | |
| 				arg: 'COLLECTION | collection',
 | |
| 				//default: 'ALL',
 | |
| 				valueRequired: false, },
 | |
| 			//*/
 | |
| 			'@to': {
 | |
| 				doc: 'destination path',
 | |
| 				arg: 'PATH | path',
 | |
| 				required: true,
 | |
| 				valueRequired: true, },
 | |
| 			// bool options...
 | |
| 			// XXX these should get defaults from .config
 | |
| 			'-include-virtual': {
 | |
| 				doc: 'include virtual blocks',
 | |
| 				arg: '| include-virtual',
 | |
| 				type: 'bool',
 | |
| 				//value: true, 
 | |
| 				default: true, },
 | |
| 			'-clean-target': {
 | |
| 				doc: 'cleanup target before export (backup)',
 | |
| 				arg: '| clean-target',
 | |
| 				type: 'bool',
 | |
| 				//value: true,
 | |
| 				default: true, },
 | |
| 			'-no-*': {
 | |
| 				doc: 'negate boolean option value',
 | |
| 				handler: function(rest, key, value, ...args){
 | |
| 					rest.unshift(key.replace(/^-?-no/, '') +'=false') } },
 | |
| 			// options...
 | |
| 			'-image-name': {
 | |
| 				doc: 'image name pattern',
 | |
| 				arg: 'PATTERN | preview-name-pattern',
 | |
| 				default: '%(fav)l%n%(-%c)c',
 | |
| 				valueRequired: true, },
 | |
| 			'-mode': { 
 | |
| 				// XXX get doc values from system...
 | |
| 				doc: 'export mode, can be "resize" or "copy best match"', 
 | |
| 				arg: 'MODE | export-mode',
 | |
| 				//default: 'copy best match',
 | |
| 				default: 'resize',
 | |
| 				valueRequired: true, },
 | |
| 			'-image-size': {
 | |
| 				doc: 'output image size',
 | |
| 				arg: 'SIZE | preview-size',
 | |
| 				default: 1000,
 | |
| 				valueRequired: true, },
 | |
| 		})},
 | |
| 		function(path, options={}){
 | |
| 			var that = this
 | |
| 
 | |
| 			// XXX SETUP
 | |
| 			this.setupFeatures()
 | |
| 
 | |
| 			path = path || options.from
 | |
| 			path = util.normalizePath(
 | |
| 				path ?
 | |
| 					pathlib.resolve(process.cwd(), path)
 | |
| 					: process.cwd())
 | |
| 
 | |
| 			var collection = options.collection
 | |
| 
 | |
| 			return this.loadIndex(path)
 | |
| 				.then(
 | |
| 					function(){
 | |
| 						// export collection...
 | |
| 						if(collection){
 | |
| 							if(!that.collections[collection]){
 | |
| 								console.error(
 | |
| 									'Can\'t find collection "'+collection+'" in index at:', path)
 | |
| 								// XXX how do we handle rejection???
 | |
| 								//return Promise.reject('moo') 
 | |
| 								return } 
 | |
| 							var resolve
 | |
| 							var reject
 | |
| 							// XXX add a timeout???
 | |
| 							that.one('collectionLoading.post', 
 | |
| 								function(){
 | |
| 									resolve(that.exportImages(options)) })
 | |
| 							that.loadCollection(collection)
 | |
| 							return new Promise(function(res, rej){
 | |
| 								resolve = res
 | |
| 								reject = rej }) }
 | |
| 						// export root...
 | |
| 						return that.exportImages(options) },
 | |
| 					function(err){
 | |
| 						// XXX how do we handle rejection???
 | |
| 						console.error('Can\'t find or load index at:', path) }) }],
 | |
| 
 | |
| 
 | |
| 	cliRepairIndex: ['- System/CLI/repair index',
 | |
| 		{cli: argv && argv.Parser({
 | |
| 			key: '@repair',
 | |
| 			doc: 'repair index',
 | |
| 			arg: 'PATH',
 | |
| 
 | |
| 			'-version': undefined,
 | |
| 			'-quiet': undefined,
 | |
| 
 | |
| 			'-read-only': '-ro',
 | |
| 			'-ro': {
 | |
| 				doc: 'only show possible fixes',
 | |
| 				type: 'bool',
 | |
| 			},
 | |
| 
 | |
| 		})},
 | |
| 		async function(path, options){
 | |
| 			this.setupFeatures()
 | |
| 
 | |
| 			await this.loadIndex(path ?? '.')
 | |
| 
 | |
| 			var changes = await this.checkIndex()
 | |
| 
 | |
| 			// XXX print...
 | |
| 			console.log(options.ro, changes)
 | |
| 
 | |
| 			options.ro
 | |
| 				//|| this.saveIndexHere()
 | |
| 				|| console.log('save')
 | |
| 	   	}],
 | |
| 
 | |
| 
 | |
| 	// XXX this is still wrong...
 | |
| 	_cliMakeIndex: ['- System/',
 | |
| 		`chain: [
 | |
| 			"loadImages: $1",
 | |
| 			"saveIndex",
 | |
| 			"makePreviews: 'all'",
 | |
| 			"sortImages",
 | |
| 			"saveIndex", ]`],
 | |
| 
 | |
| 	cliCleanIndex: ['- System/',
 | |
| 		{},
 | |
| 		function(path, options){}],
 | |
| 
 | |
| 	/* XXX
 | |
| 	cliStartServer: ['- System/CLI/start as server',
 | |
| 		{cli: '-server'},
 | |
| 		function(){
 | |
| 			// XXX
 | |
| 		}],
 | |
| 
 | |
| 	// Actions...
 | |
| 	//
 | |
| 	// XXX
 | |
| 	// XXX this should be a nested parser...
 | |
| 	// 		args:
 | |
| 	// 			from=PATH
 | |
| 	// 			to=PATH
 | |
| 	// 			...
 | |
| 	cliExportIindex: ['- System/CLI/clone index',
 | |
| 		{cli: {
 | |
| 			name: '@clone',
 | |
| 			arg: 'PATH',
 | |
| 			valueRequired: true,
 | |
| 		}},
 | |
| 		function(){
 | |
| 			// XXX
 | |
| 		}],
 | |
| 	cliPullChanges: ['- System/CLI/pull changes',
 | |
| 		{cli: {
 | |
| 			name: '@pull',
 | |
| 			arg: 'PATH',
 | |
| 			valueRequired: true,
 | |
| 		}},
 | |
| 		function(){
 | |
| 			// XXX
 | |
| 		}],
 | |
| 	cliPushChanges: ['- System/CLI/push changes',
 | |
| 		{cli: {
 | |
| 			name: '@push',
 | |
| 			arg: 'PATH',
 | |
| 			valueRequired: true,
 | |
| 		}},
 | |
| 		function(){
 | |
| 			// XXX
 | |
| 		}],
 | |
| 	//*/
 | |
| 
 | |
| })
 | |
| 
 | |
| 
 | |
| // 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...
 | |
| 		// XXX
 | |
| 		['ready',
 | |
| 			function(){
 | |
| 				var that = this
 | |
| 
 | |
| 				//var pkg = require('package.json')
 | |
| 				var pkg = nodeRequire('./package.json')
 | |
| 				var wait_for = []
 | |
| 				// XXX
 | |
| 				var interactive = false
 | |
| 
 | |
| 				// XXX SETUP need to setup everything that has command-line features...
 | |
| 				//this.setupFeatures()
 | |
| 
 | |
| 				// revise name...
 | |
| 				argv.Parser({
 | |
| 						context: this,
 | |
| 
 | |
| 						// XXX argv.js is not picking these up because 
 | |
| 						// 		of the require(..) mixup...
 | |
| 						author: pkg.author,
 | |
| 						version: pkg.version,
 | |
| 						license: pkg.license,
 | |
| 
 | |
| 						// examples...
 | |
| 						examples: CLIActions.cliExamples ?
 | |
| 							CLIActions.cliExamples.flat()
 | |
| 							: null,
 | |
| 
 | |
| 						'-verbose': {
 | |
| 							doc: 'enable (very) verbose output',
 | |
| 							handler: function(){
 | |
| 								that.logger 
 | |
| 									&& (that.logger.quiet = false) } },
 | |
| 						// XXX merge this with -quiet...
 | |
| 						'-no-progress': {
 | |
| 							doc: 'disable progress bar display',
 | |
| 							handler: function(){
 | |
| 								that.__progress = false } },
 | |
| 
 | |
| 						// XXX setup presets...
 | |
| 						//		...load sets of features and allow user 
 | |
| 						//		to block/add specific features...
 | |
| 
 | |
| 						// XXX config editor...
 | |
| 						// 		...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.key || cmd.name)
 | |
| 
 | |
| 								// skip interactive commands in non-interactive 
 | |
| 								// contexts...
 | |
| 								if(!interactive && cmd.interactive){
 | |
| 									return res }
 | |
| 
 | |
| 								res[name] = cmd instanceof argv.Parser ?
 | |
| 									// parser...
 | |
| 									cmd
 | |
| 										.then(function(unhandled, value, rest){
 | |
| 											wait_for.push(that[action](value, this)) })
 | |
| 									// single option definition...
 | |
| 									: {
 | |
| 										doc: (that.getActionAttr(action, 'doc') || '')
 | |
| 											.split(/[\\\/]/g).pop(),
 | |
| 										handler: function(rest, key, value){
 | |
| 											var res = that[action](value) 
 | |
| 											wait_for.push(res)
 | |
| 											return res },
 | |
| 										...cmd,
 | |
| 									}
 | |
| 
 | |
| 								return res }, {}),
 | |
| 					})
 | |
| 					.onArgs(function(){
 | |
| 						that.argv = this })
 | |
| 					.onNoArgs(function(args){
 | |
| 						console.log('No args.')
 | |
| 
 | |
| 						// XXX we should either start the GUI here or print help...
 | |
| 						args.push('-h')
 | |
| 						//args.push('gui')
 | |
| 					})
 | |
| 					.stop(function(){ process.exit() })
 | |
| 					.error(function(){ process.exit() })
 | |
| 					.then(function(){
 | |
| 						// XXX
 | |
| 					})()
 | |
| 
 | |
| 				// XXX not all promises in the system resolve strictly 
 | |
| 				// 		after all the work is done, some resolve before that
 | |
| 				// 		point and this calling process.exit() will interrupt 
 | |
| 				// 		them...
 | |
| 				this.__keep_running
 | |
| 					|| this.afterAction(function(){ this.stop() }) }],
 | |
| 	],
 | |
| })
 | |
| 
 | |
| 
 | |
| 
 | |
| /**********************************************************************
 | |
| * vim:set ts=4 sw=4 :                                                */
 | |
| return module })
 |