Rewrite the type builtin in rust

This commit is contained in:
Fabian Boehm 2023-02-24 21:14:13 +01:00
parent 7c37b681b2
commit 662a4740e2
10 changed files with 256 additions and 241 deletions

View file

@ -108,7 +108,7 @@ set(FISH_BUILTIN_SRCS
src/builtins/jobs.cpp src/builtins/math.cpp src/builtins/path.cpp src/builtins/jobs.cpp src/builtins/math.cpp src/builtins/path.cpp
src/builtins/read.cpp src/builtins/set.cpp src/builtins/read.cpp src/builtins/set.cpp
src/builtins/set_color.cpp src/builtins/source.cpp src/builtins/status.cpp src/builtins/set_color.cpp src/builtins/source.cpp src/builtins/status.cpp
src/builtins/string.cpp src/builtins/test.cpp src/builtins/type.cpp src/builtins/ulimit.cpp src/builtins/string.cpp src/builtins/test.cpp src/builtins/ulimit.cpp
) )
# List of other sources. # List of other sources.

View file

@ -12,4 +12,5 @@ pub mod pwd;
pub mod random; pub mod random;
pub mod realpath; pub mod realpath;
pub mod r#return; pub mod r#return;
pub mod r#type;
pub mod wait; pub mod wait;

View file

@ -38,6 +38,8 @@ pub const BUILTIN_ERR_NOT_NUMBER: &str = "%ls: %ls: invalid integer\n";
pub const BUILTIN_ERR_ARG_COUNT1: &str = "%ls: expected %d arguments; got %d\n"; pub const BUILTIN_ERR_ARG_COUNT1: &str = "%ls: expected %d arguments; got %d\n";
pub const BUILTIN_ERR_COMBO: &str = "%ls: invalid option combination\n";
// Return values (`$status` values for fish scripts) for various situations. // Return values (`$status` values for fish scripts) for various situations.
/// The status code used for normal exit in a command. /// The status code used for normal exit in a command.
@ -153,6 +155,7 @@ pub fn run_builtin(
RustBuiltin::Random => super::random::random(parser, streams, args), RustBuiltin::Random => super::random::random(parser, streams, args),
RustBuiltin::Realpath => super::realpath::realpath(parser, streams, args), RustBuiltin::Realpath => super::realpath::realpath(parser, streams, args),
RustBuiltin::Return => super::r#return::r#return(parser, streams, args), RustBuiltin::Return => super::r#return::r#return(parser, streams, args),
RustBuiltin::Type => super::r#type::r#type(parser, streams, args),
RustBuiltin::Wait => wait::wait(parser, streams, args), RustBuiltin::Wait => wait::wait(parser, streams, args),
RustBuiltin::Printf => printf::printf(parser, streams, args), RustBuiltin::Printf => printf::printf(parser, streams, args),
} }

View file

@ -0,0 +1,233 @@
use libc::c_int;
use libc::isatty;
use libc::STDOUT_FILENO;
use crate::builtins::shared::{
builtin_missing_argument, builtin_print_help, builtin_unknown_option, io_streams_t,
BUILTIN_ERR_COMBO, STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_INVALID_ARGS,
};
use crate::ffi::parser_t;
use crate::ffi::Repin;
use crate::ffi::{
builtin_exists, colorize_shell, function_get_annotated_definition,
function_get_copy_definition_file, function_get_copy_definition_lineno,
function_get_definition_file, function_get_definition_lineno, function_get_props_autoload,
function_is_copy, path_get_paths_ffi,
};
use crate::wchar::{wstr, WString, L};
use crate::wchar_ffi::WCharFromFFI;
use crate::wchar_ffi::WCharToFFI;
use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t};
use crate::wutil::{sprintf, wgettext, wgettext_fmt};
#[derive(Default)]
struct type_cmd_opts_t {
all: bool,
short_output: bool,
no_functions: bool,
get_type: bool,
path: bool,
force_path: bool,
print_help: bool,
query: bool,
}
pub fn r#type(
parser: &mut parser_t,
streams: &mut io_streams_t,
argv: &mut [&wstr],
) -> Option<c_int> {
let cmd = argv[0];
let argc = argv.len();
let print_hints = false;
let mut opts: type_cmd_opts_t = Default::default();
const shortopts: &wstr = L!(":hasftpPq");
const longopts: &[woption] = &[
wopt(L!("help"), woption_argument_t::no_argument, 'h'),
wopt(L!("all"), woption_argument_t::no_argument, 'a'),
wopt(L!("short"), woption_argument_t::no_argument, 's'),
wopt(L!("no-functions"), woption_argument_t::no_argument, 'f'),
wopt(L!("type"), woption_argument_t::no_argument, 't'),
wopt(L!("path"), woption_argument_t::no_argument, 'p'),
wopt(L!("force-path"), woption_argument_t::no_argument, 'P'),
wopt(L!("query"), woption_argument_t::no_argument, 'q'),
wopt(L!("quiet"), woption_argument_t::no_argument, 'q'),
];
let mut w = wgetopter_t::new(shortopts, longopts, argv);
while let Some(c) = w.wgetopt_long() {
match c {
'a' => opts.all = true,
's' => opts.short_output = true,
'f' => opts.no_functions = true,
't' => opts.get_type = true,
'p' => opts.path = true,
'P' => opts.force_path = true,
'q' => opts.query = true,
'h' => {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], print_hints);
return STATUS_INVALID_ARGS;
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], print_hints);
return STATUS_INVALID_ARGS;
}
_ => {
panic!("unexpected retval from wgeopter.next()");
}
}
}
if opts.query as i64 + opts.path as i64 + opts.get_type as i64 + opts.force_path as i64 > 1 {
streams.err.append(wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
return STATUS_INVALID_ARGS;
}
let mut res = false;
let optind = w.woptind;
for arg in argv.iter().take(argc).skip(optind) {
let mut found = 0;
if !opts.force_path && !opts.no_functions {
let props = function_get_props_autoload(&arg.to_ffi(), parser.pin());
if !props.is_null() {
found += 1;
res = true;
// Early out - query means *any of the args exists*.
if opts.query {
return STATUS_CMD_OK;
}
if !opts.get_type {
let path = function_get_definition_file(&props).from_ffi();
let mut comment = WString::new();
if path.is_empty() {
comment.push_utfstr(&wgettext_fmt!("Defined interactively"));
} else if path == "-" {
comment.push_utfstr(&wgettext_fmt!("Defined via `source`"));
} else {
let lineno: i32 = i32::from(function_get_definition_lineno(&props));
comment.push_utfstr(&wgettext_fmt!(
"Defined in %ls @ line %d",
path,
lineno
));
}
if function_is_copy(&props) {
let path = function_get_copy_definition_file(&props).from_ffi();
if path.is_empty() {
comment.push_utfstr(&wgettext_fmt!(", copied interactively"));
} else if path == "-" {
comment.push_utfstr(&wgettext_fmt!(", copied via `source`"));
} else {
let lineno: i32 =
i32::from(function_get_copy_definition_lineno(&props));
comment.push_utfstr(&wgettext_fmt!(
", copied in %ls @ line %d",
path,
lineno
));
}
}
if opts.path {
if function_is_copy(&props) {
let path = function_get_copy_definition_file(&props).from_ffi();
streams.out.append(path);
} else {
streams.out.append(path);
}
streams.out.append(L!("\n"));
} else if !opts.short_output {
streams.out.append(wgettext_fmt!("%ls is a function", arg));
streams.out.append(wgettext_fmt!(" with definition"));
streams.out.append(L!("\n"));
let mut def = WString::new();
def.push_utfstr(&sprintf!(
"# %ls\n%ls",
comment,
function_get_annotated_definition(&props, &arg.to_ffi()).from_ffi()
));
if !streams.out_is_redirected && unsafe { isatty(STDOUT_FILENO) == 1 } {
let col = colorize_shell(&def.to_ffi(), parser.pin()).from_ffi();
streams.out.append(col);
} else {
streams.out.append(def);
}
} else {
streams.out.append(wgettext_fmt!("%ls is a function", arg));
streams.out.append(wgettext_fmt!(" (%ls)\n", comment));
}
} else if opts.get_type {
streams.out.append(L!("function\n"));
}
if !opts.all {
continue;
}
}
}
if !opts.force_path && builtin_exists(&arg.to_ffi()) {
found += 1;
res = true;
if opts.query {
return STATUS_CMD_OK;
}
if !opts.get_type {
streams.out.append(wgettext_fmt!("%ls is a builtin\n", arg));
} else if opts.get_type {
streams.out.append(wgettext!("builtin\n"));
}
if !opts.all {
continue;
}
}
let paths: Vec<WString> = path_get_paths_ffi(&arg.to_ffi(), parser).from_ffi();
for path in paths.iter() {
found += 1;
res = true;
if opts.query {
return STATUS_CMD_OK;
}
if !opts.get_type {
if opts.path || opts.force_path {
streams.out.append(sprintf!("%ls\n", path));
} else {
streams.out.append(wgettext_fmt!("%ls is %ls\n", arg, path));
}
} else if opts.get_type {
streams.out.append(L!("file\n"));
break;
}
if !opts.all {
// We need to *break* out of this loop
// and continue on to the next argument,
// otherwise we would print every other path
// for a given argument.
break;
}
}
if found == 0 && !opts.query && !opts.path {
streams.err.append(wgettext_fmt!(
"%ls: Could not find '%ls'\n",
L!("type"),
arg
));
}
}
if res {
STATUS_CMD_OK
} else {
STATUS_CMD_ERROR
}
}

View file

@ -31,6 +31,7 @@ include_cpp! {
#include "parse_constants.h" #include "parse_constants.h"
#include "parser.h" #include "parser.h"
#include "parse_util.h" #include "parse_util.h"
#include "path.h"
#include "proc.h" #include "proc.h"
#include "tokenizer.h" #include "tokenizer.h"
#include "wildcard.h" #include "wildcard.h"
@ -81,6 +82,7 @@ include_cpp! {
generate_pod!("RustFFIProcList") generate_pod!("RustFFIProcList")
generate_pod!("RustBuiltin") generate_pod!("RustBuiltin")
generate!("builtin_exists")
generate!("builtin_missing_argument") generate!("builtin_missing_argument")
generate!("builtin_unknown_option") generate!("builtin_unknown_option")
generate!("builtin_print_help") generate!("builtin_print_help")
@ -103,6 +105,9 @@ include_cpp! {
generate!("env_var_t") generate!("env_var_t")
generate!("function_properties_t")
generate!("function_properties_ref_t")
generate!("function_get_props_autoload")
generate!("function_get_definition_file") generate!("function_get_definition_file")
generate!("function_get_copy_definition_file") generate!("function_get_copy_definition_file")
generate!("function_get_definition_lineno") generate!("function_get_definition_lineno")
@ -110,6 +115,7 @@ include_cpp! {
generate!("function_get_annotated_definition") generate!("function_get_annotated_definition")
generate!("function_is_copy") generate!("function_is_copy")
generate!("function_exists") generate!("function_exists")
generate!("path_get_paths_ffi")
generate!("colorize_shell") generate!("colorize_shell")
} }
@ -288,6 +294,7 @@ impl Repin for job_t {}
impl Repin for output_stream_t {} impl Repin for output_stream_t {}
impl Repin for parser_t {} impl Repin for parser_t {}
impl Repin for process_t {} impl Repin for process_t {}
impl Repin for function_properties_ref_t {}
pub use autocxx::c_int; pub use autocxx::c_int;
pub use ffi::*; pub use ffi::*;

View file

@ -52,7 +52,6 @@
#include "builtins/status.h" #include "builtins/status.h"
#include "builtins/string.h" #include "builtins/string.h"
#include "builtins/test.h" #include "builtins/test.h"
#include "builtins/type.h"
#include "builtins/ulimit.h" #include "builtins/ulimit.h"
#include "complete.h" #include "complete.h"
#include "cxx.h" #include "cxx.h"
@ -407,7 +406,7 @@ static constexpr builtin_data_t builtin_datas[] = {
{L"test", &builtin_test, N_(L"Test a condition")}, {L"test", &builtin_test, N_(L"Test a condition")},
{L"time", &builtin_generic, N_(L"Measure how long a command or block takes")}, {L"time", &builtin_generic, N_(L"Measure how long a command or block takes")},
{L"true", &builtin_true, N_(L"Return a successful result")}, {L"true", &builtin_true, N_(L"Return a successful result")},
{L"type", &builtin_type, N_(L"Check if a thing is a thing")}, {L"type", &implemented_in_rust, N_(L"Check if a thing is a thing")},
{L"ulimit", &builtin_ulimit, N_(L"Get/set resource usage limits")}, {L"ulimit", &builtin_ulimit, N_(L"Get/set resource usage limits")},
{L"wait", &implemented_in_rust, N_(L"Wait for background processes completed")}, {L"wait", &implemented_in_rust, N_(L"Wait for background processes completed")},
{L"while", &builtin_generic, N_(L"Perform a command multiple times")}, {L"while", &builtin_generic, N_(L"Perform a command multiple times")},
@ -554,6 +553,9 @@ static maybe_t<RustBuiltin> try_get_rust_builtin(const wcstring &cmd) {
if (cmd == L"realpath") { if (cmd == L"realpath") {
return RustBuiltin::Realpath; return RustBuiltin::Realpath;
} }
if (cmd == L"type") {
return RustBuiltin::Type;
}
if (cmd == L"wait") { if (cmd == L"wait") {
return RustBuiltin::Wait; return RustBuiltin::Wait;
} }

View file

@ -121,6 +121,7 @@ enum RustBuiltin : int32_t {
Random, Random,
Realpath, Realpath,
Return, Return,
Type,
Wait, Wait,
}; };
#endif #endif

View file

@ -1,227 +0,0 @@
// Implementation of the type builtin.
#include "config.h" // IWYU pragma: keep
#include "type.h"
#include <unistd.h>
#include <memory>
#include <string>
#include <vector>
#include "../builtin.h"
#include "../common.h"
#include "../env.h"
#include "../fallback.h" // IWYU pragma: keep
#include "../function.h"
#include "../highlight.h"
#include "../io.h"
#include "../maybe.h"
#include "../parser.h"
#include "../path.h"
#include "../wgetopt.h"
#include "../wutil.h" // IWYU pragma: keep
struct type_cmd_opts_t {
bool all = false;
bool short_output = false;
bool no_functions = false;
bool type = false;
bool path = false;
bool force_path = false;
bool print_help = false;
bool query = false;
};
static const wchar_t *const short_options = L":hasftpPq";
static const struct woption long_options[] = {
{L"help", no_argument, 'h'}, {L"all", no_argument, 'a'},
{L"short", no_argument, 's'}, {L"no-functions", no_argument, 'f'},
{L"type", no_argument, 't'}, {L"path", no_argument, 'p'},
{L"force-path", no_argument, 'P'}, {L"query", no_argument, 'q'},
{L"quiet", no_argument, 'q'}, {}};
static int parse_cmd_opts(type_cmd_opts_t &opts, int *optind, int argc, const wchar_t **argv,
parser_t &parser, io_streams_t &streams) {
UNUSED(parser);
const wchar_t *cmd = argv[0];
int opt;
wgetopter_t w;
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
switch (opt) {
case 'h': {
opts.print_help = true;
break;
}
case 'a': {
opts.all = true;
break;
}
case 's': {
opts.short_output = true;
break;
}
case 'f': {
opts.no_functions = true;
break;
}
case 't': {
opts.type = true;
break;
}
case 'p': {
opts.path = true;
break;
}
case 'P': {
opts.force_path = true;
break;
}
case 'q': {
opts.query = true;
break;
}
case ':': {
streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]);
return STATUS_INVALID_ARGS;
}
case '?': {
streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, argv[w.woptind - 1]);
return STATUS_INVALID_ARGS;
}
default: {
DIE("unexpected retval from wgetopt_long");
}
}
}
*optind = w.woptind;
return STATUS_CMD_OK;
}
/// Implementation of the builtin 'type'.
maybe_t<int> builtin_type(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
UNUSED(parser);
const wchar_t *cmd = argv[0];
int argc = builtin_count_args(argv);
type_cmd_opts_t opts;
int optind;
int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams);
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
// Mutually exclusive options
if (opts.query + opts.path + opts.type + opts.force_path > 1) {
streams.err.append_format(BUILTIN_ERR_COMBO, cmd);
return STATUS_INVALID_ARGS;
}
wcstring_list_t builtins = builtin_get_names();
bool res = false;
for (int idx = optind; argv[idx]; ++idx) {
int found = 0;
const wchar_t *name = argv[idx];
// Functions
function_properties_ref_t func{};
if (!opts.force_path && !opts.no_functions &&
(func = function_get_props_autoload(name, parser))) {
++found;
res = true;
if (!opts.query && !opts.type) {
auto path = func->definition_file;
auto copy_path = func->copy_definition_file;
auto final_path = func->is_copy ? copy_path : path;
wcstring comment;
if (!path) {
append_format(comment, _(L"Defined interactively"));
} else if (*path == L"-") {
append_format(comment, _(L"Defined via `source`"));
} else {
append_format(comment, _(L"Defined in %ls @ line %d"), path->c_str(),
func->definition_lineno());
}
if (func->is_copy) {
if (!copy_path) {
append_format(comment, _(L", copied interactively"));
} else if (*copy_path == L"-") {
append_format(comment, _(L", copied via `source`"));
} else {
append_format(comment, _(L", copied in %ls @ line %d"), copy_path->c_str(),
func->copy_definition_lineno);
}
}
if (opts.path) {
if (final_path) {
streams.out.append(*final_path);
streams.out.append(L"\n");
}
} else if (!opts.short_output) {
streams.out.append_format(_(L"%ls is a function"), name);
streams.out.append(_(L" with definition"));
streams.out.append(L"\n");
wcstring def;
append_format(def, L"# %ls\n%ls", comment.c_str(),
func->annotated_definition(name).c_str());
if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) {
std::vector<highlight_spec_t> colors;
highlight_shell(def, colors, parser.context());
streams.out.append(str2wcstring(colorize(def, colors, parser.vars())));
} else {
streams.out.append(def);
}
} else {
streams.out.append_format(_(L"%ls is a function"), name);
streams.out.append_format(_(L" (%ls)\n"), comment.c_str());
}
} else if (opts.type) {
streams.out.append(L"function\n");
}
if (!opts.all) continue;
}
// Builtins
if (!opts.force_path && contains(builtins, name)) {
++found;
res = true;
if (!opts.query && !opts.type) {
streams.out.append_format(_(L"%ls is a builtin\n"), name);
} else if (opts.type) {
streams.out.append(_(L"builtin\n"));
}
if (!opts.all) continue;
}
// Commands
wcstring_list_t paths = path_get_paths(name, parser.vars());
for (const auto &path : paths) {
++found;
res = true;
if (!opts.query && !opts.type) {
if (opts.path || opts.force_path) {
streams.out.append_format(L"%ls\n", path.c_str());
} else {
streams.out.append_format(_(L"%ls is %ls\n"), name, path.c_str());
}
} else if (opts.type) {
streams.out.append(_(L"file\n"));
break;
}
if (!opts.all) break;
}
if (!found && !opts.query && !opts.path) {
streams.err.append_format(_(L"%ls: Could not find '%ls'\n"), L"type", name);
}
}
return res ? STATUS_CMD_OK : STATUS_CMD_ERROR;
}

View file

@ -1,11 +0,0 @@
// Prototypes for executing builtin_type function.
#ifndef FISH_BUILTIN_TYPE_H
#define FISH_BUILTIN_TYPE_H
#include "../maybe.h"
class parser_t;
struct io_streams_t;
maybe_t<int> builtin_type(parser_t &parser, io_streams_t &streams, const wchar_t **argv);
#endif

View file

@ -134,3 +134,9 @@ type -p other-test-type3
type -s other-test-type3 type -s other-test-type3
# CHECK: other-test-type3 is a function (Defined via `source`, copied via `source`) # CHECK: other-test-type3 is a function (Defined via `source`, copied via `source`)
touch ./test
chmod +x ./test
PATH=.:$PATH type -P test
# CHECK: ./test