Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement CLI changes #583

Merged
merged 24 commits into from Sep 7, 2023
Merged

Implement CLI changes #583

merged 24 commits into from Sep 7, 2023

Conversation

Xophmeister
Copy link
Member

@Xophmeister Xophmeister commented Jul 17, 2023

The proposed changes to the CLI are wide-reaching. This is the "super-PR", that will ultimately be merged into main, which tracks "sub-PRs" where the functionality is implemented in smaller, more-digestable chunks.

Resolves #566 (accidentally)
Resolves #580
Resolves #588
Resolves #603
Resolves #605


Extra toppings:

Footnotes

  1. The fmt subcommand now accepts directories in its list of FILES..., which it will dutifully expand; this allows you to do, for example, topiary fmt ., rather than relying on shell expansion.

  2. Configuration collation is handled slightly differently with this new interface. Previously this was a little confusing/unfamiliar.

  3. Creating a version subcommand is straightforward, but it seems redundant to have both that and the preferred --version flag. Also, as version --configuration would make no sense, it would necessitate additional complexity just for the sake of coherent help text.

  4. Subcommands can be optional in Clap, but omitting a subcommand -- as though there were a default -- and having Clap interpret that default's arguments is a bit of a hack...and it makes the help text messy. (See Default subcommand clap-rs/clap#975)

    Addendum Default subcommands and arbitrary subcommand abbreviation is considered an anti-pattern, because -- at some point -- someone may rely on this behaviour, which fossilises the interface, preventing future expansion. (See Command Line Interface Guidelines)

  5. The CI checks that the help text in the README matches the binary's help text. With subcommands, this is now a little more complicated.

* Add env feature to clap and cargo update

* Dead Code: WIP CLI changes

* Dead Code: CLI feature parity

Although see clap-rs/clap#5020

* Note about not (yet) using infer_subcommands

See clap-rs/clap#5021

* Use clap/wrap_help

Although see clap-rs/clap#5022

* WIP: Strip out previous CLI argument parser setup

[skip ci]

* WIP: Normalise arguments for caller

[skip ci]

* Don't check for file-ness in the CLI argument parser

* File and directory canonicalisation for the argument parser

* Expose CLI types so they can be used downstream
* Update CHANGELOG

* Update README

* Update script that checks that the README correlates with --help

* Make README/CLI checking script a bit clearer

* Updated usage with new collation modes

* Documented configuration collation

* Update example call sites to new CLI

* Added a migration guide for the CLI changes to the CHANGELOG
* Update TTY playground to use new interface

* Update pre-commit hook to use new interface
* Implement Display for Configuration: pretty-print TOML

* Comment out old code, stub out new interface implementation

* Additional collation mode

* Some light refactoring

* More refactoring...

* Make the config collation independent of a specified config file

As there are several potential sources of configuration, the collation
mode is relevant whether a invocation-specific configuration file is
specified, or not. This has the cheery consequence of sidestepping
the issue described in clap-rs/clap#5020.

* Add useful annotations to the computed TOML output

* Refactor 'revise' collation mode from original

* Remove unnecessary trailing \n from TOML output

* Syntax tweak
* Add tests for TOML collation

* Refactor: Update and use (non-differentiating) collation API

* Got `merge` collation working

Includes slight refactoring of Helix's TOML merging function, largely
for my sake while I was trying to understand it!

* Some sugar for your tea?
* Note about not using infer_subcommands

* --tolerate-parsing-errors doesn't make sense for visualisation

* Separate out input source types so we can create a unified interface

* Fallback to the given output path if canonicalisation fails

Resolves #588

* We're going to need an InputFile, too

* WIP: InputFile type

* Correct blunder regarding --query

Note that clap doesn't support (--foo [--bar] | --quux) groups very
cleanly; it was a bit of a hack to get this to work, with the result
being the error text being a bit off when an illegal combination is
attempted. I've attempted to compensate for this by making the long help
text quite explicit.

Also updated clap, which contains the fix for clap-rs/clap#5022

* Missed change to README

* Add note RE clap-rs/clap#4707 workaround

* Machinery to unify inputs + downstream use to reimplement visualisation

* Don't flatten-away errors

* Don't open the input file until we need to read from it

* Into InputFrom should be from &AtLeastOneInput

* Add logging

* Moar logging!1!!
* Beginnings of thread-safe language definition caching

* Expose the raw values and implement Display

* Thread-safe cache of language definitions

* WIP: Formatting

Formatting is now functional, but there remain implementation details to
sort out. Bufferin, caching and error handling, in particular...

* Add buffered reading and writing

* Make the cache operational, if not suboptimal :(

* Better error handling
* topiary fmt tests

* Fix test task in CI workflow

* Try increasing web test timeout to 30s

* Make sure GNU sed is available, even on macOS

* CLI tests for visualisation

* CLI test for configuration output
* Expose logging verbosity through CLI flag

* Updated documentation
* Lock the cache with Tokio's Mutex

* Use the entry API as it's a bit cleaner

* Remove FIXME comment

* Initialise the logger as early as possible
@Xophmeister Xophmeister marked this pull request as ready for review August 25, 2023 10:08
@ErinvanderVeen
Copy link
Member

ErinvanderVeen commented Aug 25, 2023

This isn't a review, but I noticed that this PR removes the usage of the builtin query files altogether. I think this is fine, actually, because I'm reworking them at the moment. So I think this can be reintroduced in my PR.

@Xophmeister
Copy link
Member Author

This isn't a review, but I noticed that this PR removes the usage of the builtin query files altogether. I think this is fine, actually, because I'm reworking them at the moment. So I think this can be reintroduced in my PR.

The builtin query files are still used. They're derived from the language, as before:

impl<'cfg, 'i> Inputs<'cfg> {
pub fn new<T>(config: &'cfg Configuration, inputs: &'i T) -> Self
where
&'i T: Into<InputFrom>,
{
let inputs = match inputs.into() {
InputFrom::Stdin(language, query) => {
vec![(|| {
let language = language.to_language(config);
let query = query.unwrap_or(language.query_file()?);
Ok(InputFile {
source: InputSource::Stdin,
language,
query,
})
})()]
}
InputFrom::Files(files) => files
.into_iter()
.map(|path| {
let language = Language::detect(&path, config)?;
let query = language.query_file()?;
Ok(InputFile {
source: InputSource::Disk(path, None),
language,
query,
})
})
.collect(),
};
Self(inputs)
}
}

@Xophmeister
Copy link
Member Author

I'm curious if anyone has any insights into this...

It's not clear to me how the following is allowed:

let cache = LanguageDefinitionCache::new();
let (_, mut results) = async_scoped::TokioScope::scope_and_block(|scope| {
for input in inputs {
scope.spawn(async {
// NOTE "try blocks" and "async closures" are both unstable features. As
// such, to report errors when they happen -- rather than collated at the
// end -- we have to resort to this awkward dance, so we can benefit from
// `?` syntax sugar. Rewrite with a "try block" once the feature is stable.
let result: CLIResult<()> = match input {
Ok(input) => {
let lang_def = match cache.fetch(&input).await {
Ok(lang_def) => lang_def,
Err(error) => return Err(error),
};

Ownership of cache, here, ought to be transferred to each async block that is spawned to do the formatting for that input file. However, you cannot have multiple owners, so this should raise a compilation error. Yet, it doesn't: it both compiles and works correctly.

The usual way around this is to wrap your shared state in an Arc, clone a reference and move that into each future. Something like this:

let cache = Arc::new(LanguageDefinitionCache::new());

let (_, mut results) = async_scoped::TokioScope::scope_and_block(|scope| { 
    for input in inputs {
        scope.spawn({
            let cache = Arc::clone(&cache);

             async move {
                 // ...

I don't know how the code that is checked in gets around this. I suspect async_scoped is doing some dark magick...

@ErinvanderVeen
Copy link
Member

This isn't a review, but I noticed that this PR removes the usage of the builtin query files altogether. I think this is fine, actually, because I'm reworking them at the moment. So I think this can be reintroduced in my PR.

The builtin query files are still used. They're derived from the language, as before:

impl<'cfg, 'i> Inputs<'cfg> {
pub fn new<T>(config: &'cfg Configuration, inputs: &'i T) -> Self
where
&'i T: Into<InputFrom>,
{
let inputs = match inputs.into() {
InputFrom::Stdin(language, query) => {
vec![(|| {
let language = language.to_language(config);
let query = query.unwrap_or(language.query_file()?);
Ok(InputFile {
source: InputSource::Stdin,
language,
query,
})
})()]
}
InputFrom::Files(files) => files
.into_iter()
.map(|path| {
let language = Language::detect(&path, config)?;
let query = language.query_file()?;
Ok(InputFile {
source: InputSource::Disk(path, None),
language,
query,
})
})
.collect(),
};
Self(inputs)
}
}

I'm talking about the bash(), json(), etc, functions that use the include_str! macro.

CHANGELOG.md Outdated Show resolved Hide resolved
topiary-cli/src/cli.rs Show resolved Hide resolved
topiary-cli/src/configuration.rs Show resolved Hide resolved
topiary-cli/src/io.rs Outdated Show resolved Hide resolved
topiary-cli/src/language.rs Show resolved Hide resolved
topiary-cli/src/main.rs Outdated Show resolved Hide resolved
Copy link
Member

@Niols Niols left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment on the Rust of course, but here's my review of what I could indeed read. Let me say that I am very excited about this rework!

CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
docs/0.2-0.3-migration.md Outdated Show resolved Hide resolved
docs/0.2-0.3-migration.md Outdated Show resolved Hide resolved
docs/0.2-0.3-migration.md Outdated Show resolved Hide resolved
flake.nix Outdated Show resolved Hide resolved
@Xophmeister Xophmeister merged commit 3964d3c into main Sep 7, 2023
4 checks passed
@Xophmeister Xophmeister deleted the chris/cli-changes branch September 7, 2023 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants