import { userInfo } from 'os' ; import { stat , Stats , constants } from 'fs' ; import { VFS } from '../vfs' ; import { i18n , Capabilities , CodedError , Arguments , ParsedOptions , Util } from '@kui-shell/core' ; const strings = i18n ( 'plugin-bash-like' ) ; /** Just the bits of fs.Stats that we need */ type PartialStats = Pick < Stats , 'size' | 'mtimeMs' | 'uid' | 'gid' | 'mode' > ; interface BaseStats { name : string ; path : string ; stats : PartialStats ; } interface DirentStats { isFile : boolean ; isDirectory : boolean ; isSymbolicLink : boolean ; isSpecial : boolean ; isExecutable : boolean ; permissions : string ; username : string ; mount : Pick < VFS , 'isLocal' | 'tags' | 'mountPath' > ; } interface RawGlobStats extends BaseStats { dirent : { isFile : ( ) => boolean ; isDirectory : ( ) => boolean ; isSymbolicLink : ( ) => boolean ; } ; } export interface GlobStats extends BaseStats { viewer ? : string ; nameForDisplay : string ; dirent : DirentStats ; } export interface KuiGlobOptions extends ParsedOptions { a ? : boolean ; all ? : boolean ; d ? : boolean ; l ? : boolean ; C ? : boolean ; } function formatPermissions ( stats , isFile , isDirectory , isSymbolicLink ) { const { mode } = stats ; const d = isDirectory ? 'd' : isSymbolicLink ? 'l' : isFile ? '-' : 's' ; const ur = mode & constants . S_IRUSR ? 'r' : '-' ; const uw = mode & constants . S_IWUSR ? 'w' : '-' ; const ux = mode & constants . S_IXUSR ? 'x' : '-' ; const gr = mode & constants . S_IRGRP ? 'r' : '-' ; const gw = mode & constants . S_IWGRP ? 'w' : '-' ; const gx = mode & constants . S_IXGRP ? 'x' : '-' ; const or = mode & constants . S_IROTH ? 'r' : '-' ; const ow = mode & constants . S_IWOTH ? 'w' : '-' ; const ox = mode & constants . S_IXOTH ? 'x' : '-' ; return `${ d }${ ur }${ uw }${ ux }${ gr }${ gw }${ gx }${ or }${ ow }${ ox }` ; } /**
 * promisey form of stat
 *
 */ async function isDir ( filepath ) { return new Promise ( ( resolve , reject ) => { stat ( filepath , ( err , stats ) => { if ( err ) { if ( err . code === 'ENOENT' || err . code === 'ENOTDIR' ) { resolve ( undefined ) ; } else { reject ( err ) ; } } else { resolve ( stats ) ; } } ) ; } ) ; } const nope = ( ) => false ; const yup = ( ) => true ; /**
 * Kui command for globbing readdir
 *
 */ export async function kuiglob ( vfs , { tab , argvNoOptions , parsedOptions } ) { const globby = require ( 'globby' ) ; const inputs = argvNoOptions . slice ( argvNoOptions . indexOf ( 'kuiglob' ) + 1 ) . map ( Util . expandHomeDir ) . map ( _ => _ . replace ( /([^\\])"/g , '$1' ) ) ; const stats = inputs . length === 0 ? [ ] : await Promise . all ( inputs . map ( isDir ) ) ; const startingPointsAreDirs = stats . map ( _ => _ && _ . isDirectory ( ) ) ; const toGlob = inputs . filter ( ( _ , idx ) => ! parsedOptions . d || ! startingPointsAreDirs [ idx ] ) . reduce ( ( A , _ , idx ) => { if ( ! parsedOptions . d ) { if ( startingPointsAreDirs [ idx ] ) { A . push ( _ . endsWith ( '/' ) ? `${ _ }*` : `${ _ }/*` ) ; return A ; } else if ( _ . endsWith ( '*' ) ) { A . push ( _ ) ; A . push ( `${ _ }/*` ) ; return A ; } } A . push ( _ ) ; return A ; } , [ ] ) ; const needStats = parsedOptions . l || parsedOptions . C ; const globbedEntries = toGlob . length === 0 && inputs . length > 0 ? [ ] : ( ( ( await globby ( toGlob . length === 0 ? [ '*' ] : toGlob , { onlyFiles : false , suppressErrors : true , expandDirectories : false , followSymbolicLinks : false , dot : parsedOptions . a || parsedOptions . all , stats : needStats , objectMode : ! needStats , cwd : Capabilities . isHeadless ( ) ? process . cwd ( ) : tab . state . getState ( 'plugins/plugin-bash-like' , 'v1' , 'cwd' ) } ) ) as any ) as RawGlobStats [ ] ) ; const dirEntries = ! parsedOptions . d ? [ ] : inputs . map ( ( name , idx ) => { if ( ! startingPointsAreDirs [ idx ] ) { return undefined ; } else { return { name , path : name , dirent : { isFile : nope , isSymbolicLink : nope , isDirectory : yup } , stats : stats [ idx ] } ; } } ) . filter ( _ => _ ) ; const entries = globbedEntries . concat ( dirEntries ) ; if ( entries . length === 0 && ! inputs . some ( _ => /\*/ . test ( _ ) ) && ! startingPointsAreDirs . some ( _ => _ ) ) { const error = new Error ( strings ( 'No such file or directory' , inputs . join ( ' ' ) ) ) ; error . code = 404 ; throw error ; } const user = userInfo ( ) ; return entries . map ( ( { name , path , dirent , stats } ) => { const isFile = dirent . isFile ( ) ; const isDirectory = dirent . isDirectory ( ) ; const isSymbolicLink = dirent . isSymbolicLink ( ) ; const isExecutable = stats && ! ! ( stats . mode & ( constants . S_IXUSR | constants . S_IXGRP | constants . S_IXOTH ) ) ; const isSpecial = ! isFile && ! isDirectory && ! isSymbolicLink ; const nameForDisplay = toGlob . length > 1 ? path : name ; return { name , path , nameForDisplay , stats , dirent : { isFile , isDirectory , isSymbolicLink , isExecutable , isSpecial , mount : { isLocal : vfs . isLocal , tags : vfs . tags , mountPath : vfs . mountPath } , permissions : parsedOptions . l ? formatPermissions ( stats , isFile , isDirectory , isSymbolicLink ) : '' , username : stats && user . uid === stats . uid ? user . username : '' } } ; } ) ; }