/*
 * Copyright 2020 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { userInfo } from 'os';
import { stat, constants } from 'fs';
import { i18n, isHeadless, expandHomeDir } from '@kui-shell/core';
const strings = i18n('plugin-bash-like');
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
 *
 */
function isDir(filepath) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise((resolve, reject) => {
            stat(filepath, (err, stats) => {
                if (err) {
                    if (err.code === 'ENOENT') {
                        resolve(undefined);
                    }
                    else {
                        reject(err);
                    }
                }
                else {
                    resolve(stats);
                }
            });
        });
    });
}
const nope = () => false;
const yup = () => true;
/**
 * Kui command for globbing readdir
 *
 */
function kuiglob({ tab, argvNoOptions, parsedOptions }) {
    return __awaiter(this, void 0, void 0, function* () {
        // Intentional require versus import... some typing issues right
        // now. Also intentionally lazy here, to avoid complications with
        // webpack bundling with browser targets.
        const globby = require('globby');
        // this is the list of top-level entry points the user asked us to
        // traverse
        const inputs = argvNoOptions
            .slice(argvNoOptions.indexOf('kuiglob') + 1)
            .map(expandHomeDir) // ~ -> /home/me/
            .map(_ => _.replace(/([^\\])"/g, '$1')); // /tmp/"foo bar" -> /tmp/foo bar
        const stats = inputs.length === 0 ? [] : yield Promise.all(inputs.map(isDir));
        const startingPointsAreDirs = stats.map(_ => _ && _.isDirectory());
        // assemble the list of top-level glob entry points to traverse: 1)
        // filter out directories, if the user specified -d; 2a) add a
        // trailing wildcard, for starting points that are known to be
        // directories (e.g. `ls dirname` should traverse into dirname,
        // unless -d is specified hence the first filter; 2b) add a trailing
        // wildcard, if the user specified a trailing wildcard themselves
        // (e.g. `ls foo*` should traverse into foobar/ if it is a
        // directory)
        const toGlob = inputs
            .filter((_, idx) => !parsedOptions.d || !startingPointsAreDirs[idx])
            .reduce((A, _, idx) => {
            if (!parsedOptions.d) {
                if (startingPointsAreDirs[idx]) {
                    // ls dirname -> dirname/* as the glob
                    // ls dirname/ -> dirname/* as the glob
                    A.push(_.endsWith('/') ? `${_}*` : `${_}/*`);
                    return A;
                }
                else if (_.endsWith('*')) {
                    // ls foo* -> [foo*, foo*/*]
                    A.push(_);
                    A.push(`${_}/*`);
                    return A;
                }
            }
            // otherwise, what the user typed is what we'll use as the glob
            A.push(_);
            return A;
        }, []);
        const needStats = parsedOptions.l || parsedOptions.C;
        const globbedEntries = toGlob.length === 0 && inputs.length > 0
            ? []
            : (yield globby(toGlob.length === 0 ? '*' : toGlob, {
                followSymbolicLinks: false,
                onlyFiles: false,
                dot: parsedOptions.a || parsedOptions.all,
                stats: needStats,
                objectMode: !needStats,
                cwd: isHeadless() ? process.cwd() : tab.state.cwd
            }));
        //  ^^^^^^ re: type conversion; globby type declaration issue #139
        // handle -d; fast-glob doesn't seem to handle this very well on its
        // own; it doesn't have a way for clients to express "stop
        // traversing at any directories"
        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(_ => _)) {
            // we found nothing; be careful only to report a 404 if: a) user
            // specified no wildcards b) none of the inputs were directories;
            // e.g. `ls emptyDir` will glob nothing, but shouldn't be a 404
            const error = new Error(strings('No such file or directory', inputs.join(' ')));
            error.code = 404;
            throw error;
        }
        const user = userInfo();
        return {
            mode: 'raw',
            content: 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;
                // dirent.isBlockDevice() || dirent.isCharacterDevice() || dirent.isFIFO() || dirent.isSocket()
                // e.g. ls dir1 dir2... we want to display the dir1 and dir2 as part of the response
                const nameForDisplay = toGlob.length > 1 ? path : name;
                return {
                    name,
                    path,
                    nameForDisplay,
                    stats,
                    dirent: {
                        isFile,
                        isDirectory,
                        isSymbolicLink,
                        isExecutable,
                        isSpecial,
                        permissions: parsedOptions.l ? formatPermissions(stats, isFile, isDirectory, isSymbolicLink) : '',
                        username: stats && user.uid === stats.uid ? user.username : ''
                    }
                };
            })
        };
    });
}
/**
 * Register command handlers
 *
 */
export default (commandTree) => {
    commandTree.listen('/kuiglob', kuiglob, {
        hidden: true,
        requiresLocal: true,
        flags: {
            boolean: ['a', 'all', 'd', 'l', 'C']
        }
    });
};
//# sourceMappingURL=glob.js.map