Skip to content

Minimal implementation of sys.inputs#2481

Closed
edhebi wants to merge 1 commit intotypst:mainfrom
edhebi:main
Closed

Minimal implementation of sys.inputs#2481
edhebi wants to merge 1 commit intotypst:mainfrom
edhebi:main

Conversation

@edhebi
Copy link

@edhebi edhebi commented Oct 25, 2023

Fixes #295.

This isn't prod ready by any mean but I felt like I might as well push a minimal impl to gather some discussions.

Things of interest:

  • This changes the signature of typst_library::build() which is fairly disruptive, but I feel it's ok given the fix is just adding Default::default.
  • This makes the sys module a bit special in typst_library as the arguments of build are explicitly named after it. I feel this is a good way to ensure going through the World stays the main way to handle platform-specific things, while sys becomes kind of its own thing.
  • I didn't see much discussion on the actual cli option so I went for a basic syntax to pass named values, and a more general purpose json syntax to make scripting nicer. the json syntax is currently the only way to define non-string values in the cli
  • This allocates all over the place, partly because I wasn't sure about whether I should bring EcoString in args.rs
  • There's no handling whatsoever of what happens when you have multiple inputs with the same key. currently it takes the latest in a semi arbitrary order, but maybe we want to offer more garantees, and at least we should diagnose it
  • I have no idea how to do testing for this, suggestions welcome :)

Keeping this as a draft for now, at least until the first wave of feedback

Copy link
Contributor

@mattfbacon mattfbacon left a comment

Choose a reason for hiding this comment

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

In regards to the Default::default comments, I think there could be two possible ways to improve the experience:

  1. Create a new function, build_with_sys, or the inverse build_without_sys (bikeshed names)
  2. Use a builder pattern like library::build().with_sys_args(sys_args).


static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| {
let mut lib = typst_library::build();
let mut lib = typst_library::build(Default::default());
Copy link
Contributor

Choose a reason for hiding this comment

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

Clippy doesn't like Default::default. It wants you to include the actual type name.

Copy link
Author

Choose a reason for hiding this comment

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

Really ? that's a bit silly. I didn't get that lint, did you do something special to get it ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

From that link, seems like it is restricted to when pedantic lints are enabled. Typst doesn't enable those on CI, so I'm not sure we should follow those.

With that said, changing to SysArguments::default() probably would aid readability anyway.

Copy link
Contributor

Choose a reason for hiding this comment

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

probably would aid readability anyway

That's the idea of pedantic lints. The code does exactly the same thing, just a bit clearer because people may be confused about "the default of what?" when they see this.

Comment on lines +25 to +37
scope.category("sys");
scope.define(
"version",
Version::from_iter([
env!("CARGO_PKG_VERSION_MAJOR").parse::<u32>().unwrap(),
env!("CARGO_PKG_VERSION_MINOR").parse::<u32>().unwrap(),
env!("CARGO_PKG_VERSION_PATCH").parse::<u32>().unwrap(),
]),
);
scope.define(
"inputs",
Dict::from_iter(args.inputs.into_iter().map(|(k, v)| (k.into(), v))),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a small point, but I have observed that you can get smaller and nicer output from rustfmt by doing something like

let inputs = Dict::from_iter(...);
scope.define("inputs", inputs);

Copy link
Contributor

@PgBiel PgBiel Oct 26, 2023

Choose a reason for hiding this comment

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

I think it's fine this way, since scope.define("inputs", some_expression) is already kinda saying something like inputs = some_expression, so it's readable.

At most, I could see adding some comment like "// converting keys to EcoString" or something like that (?). Or the .into() could be changed to something like FinalType::from(k).

Copy link
Contributor

Choose a reason for hiding this comment

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

It's not for clarity, just for formatting.


Self {
library: Prehashed::new(typst_library::build()),
library: Prehashed::new(typst_library::build(Default::default())),
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto about Default::default

}

let mut lib = typst_library::build();
let mut lib = typst_library::build(Default::default());
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto about Default::default

Comment on lines +84 to +95
let mut inputs = HashMap::with_capacity(command.plain_inputs.len());
for (k, v) in &command.plain_inputs {
inputs.insert(k.clone().into(), Value::Str(v.clone().into()));
}
for raw in &command.json_inputs {
let root: HashMap<EcoString, Value> =
serde_json::from_str(raw).map_err(|e| eco_format!("{e}"))?;
inputs.reserve(root.len());
for (key, val) in root {
inputs.insert(key, val);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be done more idiomatically with iterators.

Copy link
Author

Choose a reason for hiding this comment

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

Iterators get kind of annoying here because from_iter requires annotations to deduce the RandomState type, but I'll try to see if I can find a nicer way

Copy link
Contributor

Choose a reason for hiding this comment

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

That's strange. I'm surprised you can't just do .collect::<HashMap<EcoString, Value>>()

Copy link
Contributor

Choose a reason for hiding this comment

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

That's strange. I'm surprised you can't just do .collect::<HashMap<EcoString, Value>>()

I believe it should be possible (without using Dict::from_iter):

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7007968ee2585f2a7e800087bd5e9d7f

.collect() is a very powerful beast in Rust (see also a cool fun fact about .collect()).

Copy link
Contributor

Choose a reason for hiding this comment

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

That collecting iterator of Results to Result of a container thing will be necessary here considering the possibility of failure for the JSON deserialization.

let input = command.input_file.canonicalize().map_err(|_| {
eco_format!(
"input file not found (searched at {})",
command.input_file.display()
Copy link
Contributor

Choose a reason for hiding this comment

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

Trailing comma

short = 'i',
value_name = "KEY=VALUE",
action = ArgAction::Append,
value_parser = ValueParser::new(parse_input_pair)
Copy link
Contributor

Choose a reason for hiding this comment

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

Trailing comma

pub struct SysArguments {
/// A number of keyed inputs that can be provided by the platform and will appear
/// as `sys.inputs`. The main expected usecase is scripting.
pub inputs: HashMap<EcoString, Value>,
Copy link
Member

Choose a reason for hiding this comment

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

I think it could already be a Dict at this point.


/// Hook up all sys definitions.
pub(super) fn define(global: &mut Scope, args: SysArguments) {
global.category("system");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
global.category("system");
global.category("sys");


/// Arguments for the `sys` module that handle implementation-specific behaviour.
#[derive(Clone, Default)]
pub struct SysArguments {
Copy link
Member

Choose a reason for hiding this comment

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

If we have this struct, maybe we could actually allow overriding the version here and keep the default as the Cargo.toml thing?

value_name = "JSON",
action = ArgAction::Append,
)]
pub json_inputs: Vec<String>,
Copy link
Member

@laurmaedje laurmaedje Oct 31, 2023

Choose a reason for hiding this comment

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

I'm not sure I like the json inputs thing. I'd like to keep the API surface minimal and using a single normal input in combination with json.parse can already achieve the same thing (and is not format-specific).

// The key was missing or entirely blank
MissingKey,
// The value was missing or entirely blank
MissingValue,
Copy link
Member

@laurmaedje laurmaedje Oct 31, 2023

Choose a reason for hiding this comment

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

Is passing the empty string not a valid thing?

#[clap(
long = "input",
short = 'i',
value_name = "KEY=VALUE",
Copy link
Member

Choose a reason for hiding this comment

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

I'm not super familiar with typical command line argument conventions. Is this the canonical way to deal with multiple key-value inputs?

#[derive(Debug, Clone, Args)]
pub struct SharedArgs {
/// Path to input Typst file
pub input: PathBuf,
Copy link
Member

Choose a reason for hiding this comment

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

the collision between the names here is a bit unfortunate ...

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe "source" as the new name?

@laurmaedje
Copy link
Member

laurmaedje commented Oct 31, 2023

This allocates all over the place, partly because I wasn't sure about whether I should bring EcoString in args.rs

args.rs should not depend on typst because it's used in the build script. I think just String is ok here.

@laurmaedje laurmaedje added the waiting-on-author Pull request waits on author label Nov 17, 2023
@laurmaedje
Copy link
Member

@edhebi do you still plan on working on this? if not, someone else could pick it up since it's almost done.

@laurmaedje
Copy link
Member

Closing in favor of #2894 due to inactivity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Placeholders / parameters via CLI

4 participants