mpw-shell/builtins.cpp

1242 lines
27 KiB
C++

#include "mpw-shell.h"
#include "fdset.h"
#include "value.h"
#include "environment.h"
#include "error.h"
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdarg>
#include <unistd.h>
#include <strings.h>
#include "cxx/string_splitter.h"
#include "cxx/filesystem.h"
#include "cxx/mapped_file.h"
//MAXPATHLEN
#include <sys/param.h>
#include <fcntl.h>
#include "version.h"
#include "config.h"
namespace ToolBox {
std::string MacToUnix(const std::string path);
std::string UnixToMac(const std::string path);
}
namespace fs = filesystem;
namespace {
std::string &lowercase(std::string &s) {
std::transform(s.begin(), s.end(), s.begin(), [](char c){ return std::tolower(c); });
return s;
}
// doesn't handle flag arguments but builtins don't have arguments.
template<class FX>
std::vector<std::string> getopt(const std::vector<std::string> &argv, FX fx) {
std::vector<std::string> out;
out.reserve(argv.size());
std::copy_if(argv.begin()+1, argv.end(), std::back_inserter(out), [&fx](const std::string &s){
if (s.empty()) return false; // ?
if (s.front() == '-') {
std::for_each(s.begin() + 1, s.end(), fx);
return false;
}
return true;
});
return out;
}
template <class T>
class offset_range {
public:
typedef typename T::iterator iterator;
typedef typename T::const_iterator const_iterator;
offset_range(T &t, size_t offset) : _t(t), _offset(offset)
{}
offset_range(const offset_range &) = default;
offset_range(offset_range &&) = default;
offset_range() = delete;
auto begin() { return std::next(_t.begin(), _offset); }
auto begin() const { return std::next(_t.begin(), _offset); }
auto cbegin() { return std::next(_t.begin(), _offset); }
auto end() { return _t.end(); }
auto end() const { return _t.end(); }
auto cend() const { return _t.cend(); }
private:
T &_t;
size_t _offset;
};
template<class T>
offset_range<T> make_offset_range(T &t, size_t offset) {
return offset_range<T>(t, offset);
}
}
#undef stdin
#undef stdout
#undef stderr
#define stdin fds[0]
#define stdout fds[1]
#define stderr fds[2]
#undef fprintf
#undef fputs
#undef fputc
#define fprintf DO_NOT_USE_FPRINTF
#define fputs DO_NOT_USE_FPUTS
#define fputc DO_NOT_USE_FPUTC
inline int fdputs(const char *data, int fd) {
auto rv = write(fd, data, strlen(data));
return rv < 0 ? EOF : rv;
}
inline int fdputs(const std::string &s, int fd) {
auto rv = write(fd, s.data(), s.size());
return rv < 0 ? EOF : rv;
}
inline int fdputc(int c, int fd) {
unsigned char tmp = c;
auto rv = write(fd, &tmp, 1);
return rv < 0 ? EOF : c;
}
#ifdef HAVE_DPRINTF
#define fdprintf dprintf
#else
inline int fdprintf(int fd, const char *format, ...) {
char *cp = nullptr;
va_list ap;
va_start(ap, format);
int len = vasprintf(&cp, format, ap);
va_end(ap);
fdputs(cp, fd);
free(cp);
return len;
}
#endif
std::vector<std::string> load_argv(Environment &env) {
std::vector<std::string> rv;
int n = env.pound();
if ( n <= 0 ) return rv;
n = std::min(n, (int)255);
rv.reserve(n);
for (int i = 1; i <= n; ++i) {
rv.push_back(env.get(std::to_string(i)));
}
return rv;
}
int builtin_shift(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
int n = 1;
if (tokens.size() > 3) {
fdputs("### Shift - Too many parameters were specified.\n", stderr);
fdputs("# Usage - Shift [number]\n", stderr);
return 1;
}
if (tokens.size() == 2) {
value v(tokens[1]);
if (v.is_number() && v.number >= 0) n = v.number;
else {
fdputs("### Shift - The parameter must be a positive number.\n", stderr);
fdputs("# Usage - Shift [number]\n", stderr);
return 1;
}
}
if (n == 0) return 0;
env.shift(n);
#if 0
auto argv = load_argv(env);
if (argv.empty()) return 0;
std::move(argv.begin() + n , argv.end(), argv.begin());
do {
env.unset(std::to_string(argv.size()));
argv.pop_back();
} while (--n);
env.set_argv(argv);
#endif
return 0;
}
int builtin_unset(Environment &env, const std::vector<std::string> &tokens, const fdmask &) {
for (const auto &s : make_offset_range(tokens, 1)) {
env.unset(s);
}
// unset [no arg] removes ALL variables
if (tokens.size() == 1) {
env.unset();
}
return 0;
}
int builtin_set(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
// set var name -- set
// set var -- just print the value
// 3.5 supports -e to also export it.
//io_helper io(fds);
if (tokens.size() == 1) {
for (const auto &kv : env) {
std::string name = quote(kv.first);
std::string value = quote(kv.second);
fdprintf(stdout, "Set %s%s %s\n",
bool(kv.second) ? "-e " : "",
name.c_str(), value.c_str());
}
return 0;
}
if (tokens.size() == 2) {
std::string name = tokens[1];
auto iter = env.find(name);
if (iter == env.end()) {
fdprintf(stderr, "### Set - No variable definition exists for %s.\n", name.c_str());
return 2;
}
name = quote(name);
std::string value = quote(iter->second);
fdprintf(stdout, "Set %s%s %s\n",
bool(iter->second) ? "-e " : "",
name.c_str(), value.c_str());
return 0;
}
bool exported = false;
if (tokens.size() == 4 && tokens[1] == "-e") {
exported = true;
}
if (tokens.size() > 3 && !exported) {
fdputs("### Set - Too many parameters were specified.\n", stderr);
fdputs("# Usage - set [name [value]]\n", stderr);
return 1;
}
std::string name = tokens[1+exported];
std::string value = tokens[2+exported];
env.set(name, value, exported);
return 0;
}
static int export_common(Environment &env, bool export_or_unexport, const std::vector<std::string> &tokens, const fdmask &fds) {
const char *name = export_or_unexport ? "Export" : "Unexport";
bool _r = false;
bool _s = false;
bool error = false;
auto argv = getopt(tokens, [&](char c){
switch(tolower(c)) {
case 'r': _r = true; break;
case 's': _s = true; break;
default:
fdprintf(stderr, "### %s - \"-%c\" is not an option.\n", name, c);
error = true;
break;
}
});
if (error) {
fdprintf(stderr, "# Usage - %s [-r | -s | name...]\n", name);
return 1;
}
if (argv.empty()) {
if (_r && _s) goto conflict;
// list of exported vars.
// -r will generate unexport commands for exported variables.
// -s will only print the names.
name = export_or_unexport ? "Export " : "Unexport ";
for (const auto &kv : env) {
const std::string& vname = kv.first;
if (kv.second == export_or_unexport)
fdprintf(stdout, "%s%s\n", _s ? "" : name, quote(vname).c_str());
}
return 0;
}
else {
// mark as exported.
if (_r || _s) goto conflict;
for (std::string s : argv) {
auto iter = env.find(s);
if (iter != env.end()) iter->second = export_or_unexport;
}
return 0;
}
conflict:
fdprintf(stderr, "### %s - Conflicting options or parameters were specified.\n", name);
fdprintf(stderr, "# Usage - %s [-r | -s | name...]\n", name);
return 1;
}
int builtin_export(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
return export_common(env, true, tokens, fds);
}
int builtin_unexport(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
return export_common(env, false, tokens, fds);
}
int builtin_alias(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
// alias -> lists all aliases
// alias name -> list single alias
// alias name parms... -> add a new alias.
if (tokens.size() == 1) {
for (const auto &p : env.aliases()) {
fdprintf(stdout, "Alias %s %s\n", quote(p.first).c_str(), quote(p.second).c_str());
}
return 0;
}
std::string name = tokens[1];
if (tokens.size() == 2) {
const auto as = env.find_alias(name);
if (as.empty()) {
fdprintf(stderr, "### Alias - No alias exists for %s\n", quote(name).c_str());
return 1;
}
fdprintf(stdout, "Alias %s %s\n", quote(name).c_str(), quote(as).c_str());
return 0;
}
std::string as;
for (const auto &s : make_offset_range(tokens, 2)) {
as += s;
as.push_back(' ');
}
as.pop_back();
// add/remove it to the alias table...
if (as.empty()) {
env.remove_alias(name);
}
else {
env.add_alias(std::move(name), std::move(as));
}
return 0;
}
int builtin_unalias(Environment &env, const std::vector<std::string> &tokens, const fdmask &) {
// unalias -> remove all aliases.
// unalias name -> remove single alias.
if (tokens.size() == 1) {
env.remove_alias();
return 0;
}
for (const auto &x : make_offset_range(tokens, 1)) {
env.remove_alias(x);
}
return 0;
}
int builtin_echo(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
//io_helper io(fds);
bool space = false;
bool n = false;
for (const auto &s : make_offset_range(tokens, 1)) {
if (s == "-n" || s == "-N") {
n = true;
continue;
}
if (space) {
fdputs(" ", stdout);
}
fdputs(s.c_str(), stdout);
space = true;
}
if (!n) fdputs("\n", stdout);
return 0;
}
int builtin_quote(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
// todo...
//io_helper io(fds);
bool space = false;
bool n = false;
for (const auto &s : make_offset_range(tokens, 1)) {
if (s == "-n" || s == "-N") {
n = true;
continue;
}
if (space) {
fdputs(" ", stdout);
}
fdputs(quote(s).c_str(), stdout);
space = true;
}
if (!n) fdputs("\n", stdout);
return 0;
}
int builtin_parameters(Environment &env, const std::vector<std::string> &argv, const fdmask &fds) {
//io_helper io(fds);
int i = 0;
for (const auto &s : argv) {
fdprintf(stdout, "{%d} %s\n", i++, s.c_str());
}
return 0;
}
int builtin_directory(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
// directory [-q]
// directory path
// for relative names, uses {DirectoryPath} (if set) rather than .
// set DirectoryPath ":,{MPW},{MPW}Projects:"
/*
* Parameters:
* ----------
* directory
* Sets the default directory to directory. If you specify directory
* as a leafname (that is, the final portion of a full pathname), the
* MPW Shell searches for the directory in the current directory path
* (for example, searching "{MPW}Examples:" for CExamples). However, if
* the MPW Shell fails to find the directory in the current directory
* path, it searches the directories listed in the {DirectoryPath} MPW
* Shell variable, which contains a list of directories to be searched
* in order of precedence. The last example illustrates how to do this.
*
* Options:
* -------
* -q
* Inhibits quoting the directory pathname written to standard
* output. This option applies only if you omit the directory
* parameter Normally the MPW Shell quotes the current default
* directory name if it contains spaces or other special characters
*
* Status:
* ------
* Directory can return the following status codes:
*
* 0 no errors
* 1 directory not found; command aborted; or parameter error
*
*/
//io_helper io(fds);
bool q = false;
bool error = false;
std::vector<std::string> argv = getopt(tokens, [&](char c){
switch(tolower(c))
{
case 'q': q = true; break;
default:
fdprintf(stderr, "### Directory - \"-%c\" is not an option.\n", c);
error = true;
break;
}
});
if (error) {
fdputs("# Usage - Directory [-q | directory]\n", stderr);
return 1;
}
if (argv.size() > 1) {
fdputs("### Directory - Too many parameters were specified.\n", stderr);
fdputs("# Usage - Directory [-q | directory]\n", stderr);
return 1;
}
if (argv.size() == 1) {
//cd
if (q) {
fdputs("### Directory - Conflicting options or parameters were specified.\n", stderr);
return 1;
}
// todo -- if relative path does not exist, check {DirectoryPath}
fs::path path = ToolBox::MacToUnix(argv.front());
std::error_code ec;
current_path(path, ec);
if (ec) {
fdputs("### Directory - Unable to set current directory.\n", stderr);
fdprintf(stderr, "# %s\n", ec.message().c_str());
return 1;
}
}
else {
// pwd
std::error_code ec;
fs::path path = fs::current_path(ec);
if (ec) {
fdputs("### Directory - Unable to get current directory.\n", stderr);
fdprintf(stderr, "# %s\n", ec.message().c_str());
return 1;
}
// todo -- pathname translation?
std::string s = path;
if (!q) s = quote(std::move(s));
fdprintf(stdout, "%s\n", s.c_str());
}
return 0;
}
int builtin_exists(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
bool _a = false; // print if alias/symlink
bool _d = false; // print if directory
bool _f = false; // print if normal file
bool _n = false; // don't follow alias
bool _w = false; // print if normal file + writable
bool _q = false; // don't quote names
bool error = false;
std::vector<std::string> argv = getopt(tokens, [&](char c){
switch(tolower(c))
{
case 'a': _a = true; break;
case 'd': _d = true; break;
case 'f': _f = true; break;
case 'n': _n = true; break;
case 'q': _q = true; break;
case 'w': _w = true; break;
default:
fdprintf(stderr, "### Exists - \"-%c\" is not an option.\n", c);
error = true;
break;
}
});
if (_w) _f = false;
if (_a + _d + _f + _w > 1) {
fdputs("### Exists - Conflicting options were specified.\n", stderr);
error = true;
}
if (argv.size() < 1) {
fdputs("### Exists - Not enough parameters were specified.\n", stderr);
error = true;
}
if (error) {
fdputs("# Usage - Exists [-a | -d | -f | -w] [-n] [-q] name...\n", stderr);
return 1;
}
for (auto &s : argv) {
std::string path = ToolBox::MacToUnix(s);
std::error_code ec;
fs::file_status st = _n ? fs::symlink_status(path, ec) : fs::status(path, ec);
if (ec) continue;
if (_d && !fs::is_directory(st)) continue;
if (_a && !fs::is_symlink(st)) continue;
if (_f && !fs::is_regular_file(st)) continue;
if (_w && !fs::is_regular_file(st)) continue;
if (_w && (access(path.c_str(), W_OK | F_OK) < 0)) continue;
if (!_q) s = quote(std::move(s));
fdprintf(stdout, "%s\n", s.c_str());
}
return 0;
}
static bool is_assignment(int type) {
switch(type)
{
case '=':
case '+=':
case '-=':
return true;
default:
return false;
}
}
int builtin_evaluate(Environment &env, std::vector<token> &&tokens, const fdmask &fds) {
// evaluate expression
// evaluate variable = expression
// evaluate variable += expression
// evaluate variable -= expression
// flags -- -h -o -b -- print in hex, octal, or binary
// convert the arguments to a stack.
int output = 'd';
//io_helper io(fds);
std::reverse(tokens.begin(), tokens.end());
// remove 'Evaluate'
tokens.pop_back();
// check for -h -x -o
if (tokens.size() >= 2 && tokens.back().type == '-') {
const token &t = tokens[tokens.size() - 2];
if (t.type == token::text && t.string.length() == 1) {
int flag = tolower(t.string[0]);
switch(flag) {
case 'o':
case 'h':
case 'b':
output = flag;
tokens.pop_back();
tokens.pop_back();
}
}
}
if (tokens.size() >= 2 && tokens.back().type == token::text)
{
int type = tokens[tokens.size() -2].type;
if (is_assignment(type)) {
std::string name = tokens.back().string;
tokens.pop_back();
tokens.pop_back();
int32_t i = evaluate_expression(env, "Evaluate", std::move(tokens));
switch(type) {
case '=':
env.set(name, i);
break;
case '+=':
case '-=':
{
value old;
auto iter = env.find(name);
if (iter != env.end()) old = (const std::string &)iter->second;
switch(type) {
case '+=':
i = old.to_number() + i;
break;
case '-=':
i = old.to_number() - i;
break;
}
env.set(name, i);
}
break;
}
return 0;
}
}
int32_t i = evaluate_expression(env, "Evaluate", std::move(tokens));
if (output == 'h') {
fdprintf(stdout, "0x%08x\n", i);
return 0;
}
if (output == 'b') {
std::string tmp("0b");
for (int j = 0; j < 32; ++j) {
tmp.push_back(i & 0x80000000 ? '1' : '0');
i <<= 1;
}
tmp.push_back('\n');
fdputs(tmp.c_str(), stdout);
return 0;
}
if (output == 'o') {
// octal.
fdprintf(stdout, "0%o\n", i);
return 0;
}
fdprintf(stdout, "%d\n", i);
return 0;
}
int builtin_which(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
// which [-a] [-p] [command]
bool _a = false;
bool _p = false;
bool error = false;
std::vector<std::string> argv = getopt(tokens, [&](char c){
switch(tolower(c))
{
case 'a': _a = true; break;
case 'p': _p = true; break;
default:
fdprintf(stderr, "### Which - \"-%c\" is not an option.\n", c);
error = true;
break;
}
});
if (argv.size() > 1) {
fdprintf(stderr, "### Which - Too many parameters were specified.\n");
error = true;
}
if (error) {
fdprintf(stderr, "# Usage - Which [-a] [-p] [name]\n");
return 1;
}
std::string s = env.get("commands");
string_splitter ss(s, ',');
if (argv.empty()) {
// just print the paths.
for (; ss; ++ss) {
fdprintf(stdout, "%s\n", ss->c_str());
}
return 0;
}
std::string target = argv[0];
bool found = false;
// if absolute or relative path, check that.
if (target.find_first_of("/:") != target.npos) {
std::error_code ec;
fs::path p(ToolBox::MacToUnix(target));
if (fs::exists(p, ec)) {
fdprintf(stdout, "%s\n", quote(p).c_str());
return 0;
}
else {
fdprintf(stderr, "### Which - File \"%s\" not found.\n", target.c_str());
return 2;
}
}
for(; ss; ++ss) {
if (_p) fdprintf(stderr, "checking %s\n", ss->c_str());
std::error_code ec;
fs::path p(ToolBox::MacToUnix(ss->c_str()));
p /= target;
if (fs::exists(p, ec)) {
found = true;
fdprintf(stdout, "%s\n", quote(p).c_str());
if (!_a) break;
}
}
// check builtins...
if (!found || _a) {
static const char *builtins[] = {
"aboutbox",
"alias",
"catenate",
"directory",
"echo",
"execute",
"exists",
"export",
"help",
"false", // not in MPW
"parameters",
"quote",
"set",
"shift",
"true", // not in MPW
"unalias",
"unexport",
"unset",
"version",
"which",
};
lowercase(target);
auto iter = std::find(std::begin(builtins), std::end(builtins), target);
if (iter != std::end(builtins)) {
fdprintf(stdout, "%s\n", *iter);
found = true;
}
}
if (found) return 0;
// also check built-ins?
fdprintf(stderr, "### Which - Command \"%s\" was not found.\n", target.c_str());
return 2; // not found.
}
int cat_helper(int in, int out) {
static uint8_t buffer[4096];
for(;;) {
ssize_t rcount = read(in, buffer, sizeof(buffer));
if (rcount < 0) {
if (errno == EINTR) continue;
return 2;
}
if (rcount == 0) break;
for (;;) {
ssize_t wcount = write(out, buffer, rcount);
if (wcount < 0) {
if (errno == EINTR) continue;
return 2;
}
if (wcount != rcount) return 2;
break;
}
}
return 0;
}
int builtin_catenate(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
if (tokens.size() == 1) {
int rv = cat_helper(stdin, stdout);
if (rv) fdputs("### Catenate - I/O Error\n", stderr);
return rv;
}
for (const auto &s : make_offset_range(tokens, 1)) {
std::string path = ToolBox::MacToUnix(s);
int fd = open(path.c_str(), O_RDONLY);
if (fd < 0) {
fdprintf(stderr, "### Catenate - Unable to open \"%s\".\n", path.c_str());
return 1;
}
int rv = cat_helper(fd, stdout);
close(fd);
if (rv) {
fdputs("### Catenate - I/O Error\n", stderr);
return rv;
}
}
return 0;
}
int builtin_version(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
bool _v = false;
bool error = false;
auto argv = getopt(tokens, [&](char c){
switch(tolower(c))
{
case 'v': _v = true; break;
default:
fdprintf(stderr, "### Version - \"-%c\" is not an option.\n", c);
error = true;
break;
}
});
if (argv.size() != 0) {
fdprintf(stderr, "### Version - Too many parameters were specified.\n");
error = true;
}
if (error) {
fdprintf(stderr, "# Usage - Version [-v]\n");
return 1;
}
//fdputs("MPW Shell 3.5, Copyright Apple Computer, Inc. 1985-99. All rights reserved.\n", stdout);
fdputs("MPW Shell " VERSION ", Copyright Kelvin W Sherlock 2016. All rights reserved.\n", stdout);
fdputs("based on MPW Shell 3.5, Copyright Apple Computer, Inc. 1985-99. All rights reserved.\n", stdout);
if (_v) {
fdputs("This version built on " __DATE__ " at " __TIME__ ".\n", stdout);
}
return 0;
}
int builtin_aboutbox(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
// the most important command of all!
if (tokens.size() == 2 && tokens[1] == "--moof") {
fdputs(
"\n"
" ## \n"
" ## ## #### \n"
" ## #### ## \n"
" ## ## \n"
" ## ## ## ## \n"
" ## ## #### \n"
" ## ## ## ## \n"
" ######## #### ## ## \n"
" ## #################### ## \n"
" ## ############## ## \n"
" #### ############ ## \n"
" ###### ###### ## \n"
" ###### ## \n"
" #### ## \n"
" ## ## \n"
" ## ################ ## \n"
" ## ## ## ## \n"
" ## ## ## ## \n"
" ## ## ## ## \n"
" ## ## ## ## \n"
" ## ## ## ## \n"
" ###### ###### \n"
"\n"
,stdout);
return 0;
}
fdprintf(stdout,
"+--------------------------------------+\n"
"| MPW Shell %-4s |\n"
"| |\n"
"| |\n"
"| (c) 2016 Kelvin W Sherlock |\n"
"+--------------------------------------+\n"
, VERSION);
return 0;
}
int builtin_true(Environment &, const std::vector<std::string> &, const fdmask &) {
return 0;
}
int builtin_false(Environment &, const std::vector<std::string> &, const fdmask &) {
return 1;
}
int builtin_quit(Environment &, const std::vector<std::string> &tokens, const fdmask &fds) {
bool error = false;
bool _y = false;
bool _n = false;
bool _c = false;
auto argv = getopt(tokens, [&](char c){
switch(tolower(c))
{
case 'c': _c = true; break;
case 'n': _n = true; break;
case 'y': _y = true; break;
default:
fdprintf(stderr, "### Quit - \"-%c\" is not an option.\n", c);
error = true;
break;
}
});
if (_y + _n + _c > 1) {
fdprintf(stderr, "### Quit - Conflicting options were specified.\n");
error = true;
}
if (argv.size() > 0) {
fdprintf(stderr, "### Quit - Too many parameters were specified.\n");
error = true;
}
if (error) {
fdprintf(stderr, "# Usage - Quit [-y | -n | -c]\n");
return 1;
}
throw quit_command_t{};
return 0;
}
namespace {
template<class Iter>
Iter find_entry(Iter begin, Iter end) {
for(;;) {
begin = std::find(begin, end, '\n');
if (begin == end) return end;
if (std::distance(begin, end) < 3) return end;
++begin;
if (begin[0] == '-' && begin[1] == '\n') return begin;
}
}
template<class Iter1, class Iter2>
bool help_name_match(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) {
if (std::distance(begin2, end2) <= std::distance(begin1, end1)) return false;
for( ; begin1 != end1; ++begin1, ++begin2) {
if (tolower(*begin1) != tolower(*begin2)) return false;
}
if (isspace(*begin2)) return true;
return false;
}
}
bool help_helper(const mapped_file &f, const fdmask &fds, const std::string &cmd) {
/*
* format is:
* -\n
* name whitespace
* ....
* -\n
*/
if (f.size() < 2) return false;
auto iter = f.begin();
auto end = f.end();
if (iter[0] != '-' && iter[1] != '\n') {
iter = find_entry(iter, end);
}
if (cmd.empty()) {
// print first entry
write(stdout, f.begin(), std::distance(f.begin(), iter));
fdputs("\n", stdout);
return true;
}
for(;;) {
if (iter == end) return false;
iter += 2;
auto next = find_entry(iter, end);
auto l = std::distance(iter, end);
if (help_name_match(cmd.begin(), cmd.end(), iter, next)) {
write(stdout, iter, std::distance(iter, next));
fdputs("\n", stdout);
return true;
}
iter = next;
}
}
int builtin_help(Environment &env, const std::vector<std::string> &tokens, const fdmask &fds) {
bool error = false;
filesystem::path _f;
// todo -- -f to specify help file.
auto argv = getopt(tokens, [&](char c){
switch(tolower(c))
{
default:
fdprintf(stderr, "### Help - \"-%c\" is not an option.\n", c);
error = true;
break;
}
});
if (error) {
fdputs("# Usage - Help [-f helpfile] command...\n", stderr);
return 1;
}
const filesystem::path sd(ToolBox::MacToUnix(env.get("shelldirectory")));
const filesystem::path hd = sd / "Help";
filesystem::path mono;
mapped_file mono_file;
std::error_code ec;
if (_f.empty()) {
mono = sd / "MPW.Help";
mono_file = mapped_file(mono, mapped_file::priv, ec);
} else {
mono = _f;
mono_file = mapped_file(mono, mapped_file::priv, ec);
if (!mono_file && !_f.is_absolute()) {
mono = sd / _f;
mono_file = mapped_file(mono, mapped_file::priv, ec);
}
if (!mono_file) {
fdprintf(stderr, "### Help: Unable to open %s\n", _f.c_str());
fdprintf(stderr, "# %s\n", ec.message().c_str());
return 3;
}
}
if (mono_file) {
std::replace(mono_file.begin(), mono_file.end(), '\r', '\n');
}
if (argv.empty()) {
help_helper(mono_file, fds, "");
return 0;
}
int rv = 0;
for (const auto &cmd : argv) {
// 1. check for $MPW:Help:command
filesystem::path p(hd);
p /= cmd;
mapped_file f(p, mapped_file::priv, ec);
if (!ec) {
std::replace(f.begin(), f.end(), '\r', '\n');
write(stdout, f.data(), f.size());
fdputs("\n", stdout);
continue;
}
if (mono_file) {
bool ok = help_helper(mono_file, fds, cmd);
if (ok) break;
}
fdprintf(stderr, "### Help - \"%s\" was not found.\n", cmd.c_str());
rv = 2;
}
return rv;
}
int builtin_execute(Environment &e, const std::vector<std::string> &tokens, const fdmask &fds) {
// runs argv[1] in the current environment. unlike MPW, argv[1] must be a script.
// special enhancement -- '-' means execute stdin.
if (tokens.size() < 2) return 0;
std::string filename = tokens[1];
if (filename == "-") {
// since we're parsing stdin, don't let any children read it. [???]
fdset new_fds;
int fd = open("/dev/null", O_RDONLY);
new_fds.set(0, fd);
return read_fd(e, fds[0], new_fds | fds);
}
return read_file(e, filename, fds);
}