Skip to content

config impl (pt.1) #1550

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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open

config impl (pt.1) #1550

wants to merge 15 commits into from

Conversation

Sk7Str1p3
Copy link
Contributor

@Sk7Str1p3 Sk7Str1p3 commented Mar 28, 2025

Goals

Implements advanced customisation with config file in toml format. This pull request (pt.1) aims to implement just basic configuration, and pt.2 will go much deeper with it.
Resolves #745

Todo

Target Progress
Allow user define existing cli opts in config file
  • setup config parser
  • create basic config structure
  • override config parametrs with values from CLI
Implement new cli/config options
  • modules order
    (requires major changes in logic)
  • custom functions
Advanced

upd: i doubt functions 'custom' are really necessary

@Sk7Str1p3
Copy link
Contributor Author

@spenserblack I'm sorry for pinging you that many times... I opened new PR

Current problem is config is not spawning at $XDG_CONFIG_HOME, but it spawns folder. I am not able to check how does config parsed because is not created. Waiting for your answer, thx

@spenserblack
Copy link
Collaborator

I'm sorry for pinging you that many times

No worries.

I am not able to check how does config parsed because is not created.

Since you're using serde, you should be able to read the config from a string. Or a reader type that uses a string. I'd honestly use that for testing. You can use include_str!("path/to/test-config.toml"), load the configuration from the TOML string, and test its values.

Since the CLI type implements Parser, you can also test the CLI by passing one of the iterator types (simplest being an array of strings) to .try_parse_from, which will then let you test how the CLI and config resolve together.

I'd recommend putting the tests and test config file into tests/ to organize things, because we probably don't want random .toml files in the source code.

Copy link
Collaborator

@spenserblack spenserblack left a comment

Choose a reason for hiding this comment

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

See review comment. I think it should help you figure out why the config file isn't being created.

@Sk7Str1p3
Copy link
Contributor Author

Sk7Str1p3 commented Mar 29, 2025

i think i should write a todo with what are my plans on customisation with config

config sample:

separator = " -> "
number-separator = comma

# this should support custom ascii and images
# if images not supported will fallback to custom ascii if provided and standard if not
[logo] 
ruby = ./ruby.png
c = ./c.txt
rust = { image = ./rust.png, ascii = ./rust.txt }

# like --disabled-field, but lets you change order
modules = """
  name
  break

  project
  created
  authors
  break

  head
  last-change
  pending
  churn
  lines-of-code
  break

  size
"""

# detailed settings on module
[name]
format = -----$name|$version-----

@Sk7Str1p3
Copy link
Contributor Author

Sk7Str1p3 commented Mar 29, 2025

Nah, we won't get any good customization if we just parse config as CLI arguments. I reverted all commits

I am looking forward to CrabFetch approach. All customisation made by configuration file, and CLI just lets you some minimal settings.
We can also do things like FastFetch does. It has enormous amount of CLI arguments and uses values in config as arguments. I do NOT like that.

@spenserblack what's your opinion on that?

@spenserblack
Copy link
Collaborator

Nah, we won't get any good customization if we just parse config as CLI arguments.

Could you explain this a bit more? You hint at overriding logos per-language in #1550 (comment), which wouldn't work if config and CLI are identical. Is that the main issue, or are there others?

I am looking forward to CrabFetch approach. All customisation made by configuration file, and CLI just lets you some minimal settings.
We can also do things like FastFetch does. It has enormous amount of CLI arguments and uses values in config as arguments. I do NOT like that.

Each repository is different, and can require different options. For example, if a repository has a logo, show that with -i. In many repositories, the user would want to use -T to get stats that better represent the "real" dominant language. Frankly, if I had to write a config file to set these, a config file that I may never use again (likely if I'm not a maintainer of the repo I'm analyzing), I would find it a bit annoying. IMO, simply allowing users to store and not repeat some CLI arguments is a big win. But I'm still of the opinion that the design should be CLI first, config file secondary.

@spenserblack
Copy link
Collaborator

spenserblack commented Mar 31, 2025

OK, I've just reread the idea that was documented in #745. I had kind of forgotten the original intent of that issue 😅

I've noticed that the original issue body was mainly focusing on display configuration options. This makes sense to me, since those are the types of things that I'd likely only want to set once, not per-repo. Also, some options would be kind of awkward to set via the CLI, like if I was trying to set Nerd Font icons for the info lines.

So, yeah, I'm backtracking on the config file being a 1:1 with the CLI. Instead, we should focus on a limited set of options that we're very confident would only be set once, regardless of repo. And also some options that may work best in a config file instead of as CLI arguments.

@o2sh Thoughts?

@Sk7Str1p3
Copy link
Contributor Author

Instead, we should focus on a limited set of options that we're very confident would only be set once, regardless of repo

Those could be key-value separator, number separator, nerd-fonts switch and natural colors (use colors set by terminal) switch (to be implemented).

Im working on those and possibly finish in just few hours.

@Sk7Str1p3
Copy link
Contributor Author

Sk7Str1p3 commented Mar 31, 2025

Speaking of key-value separator, check this out
IMG_20250331_081134_159.jpg

This confusing me, why that would happen? Why I could not use -> as separator?

@spenserblack
Copy link
Collaborator

spenserblack commented Mar 31, 2025

This confusing me, why that would happen? Why I could not use -> as separator?

That's going to be caused by clap, the library for argument parsing. It's reading the next argument after --separator and seeing ->. I believe from there it's checking that the next argument starts with - (it does), and interpreting that as another CLI switch/option instead of as the value passed for --separator.

There's perhaps an option that would force clap to take whatever argument is next, regardless of format.

@Sk7Str1p3
Copy link
Contributor Author

AYO I MADE BASE FOR CONFIG!!1!
currently slowly creating commits

@Sk7Str1p3
Copy link
Contributor Author

@spenserblack I got one annoying error with config file - values MUST be set there, otherwise onefetch is crashing

@spenserblack
Copy link
Collaborator

@spenserblack I got one annoying error with config file - values MUST be set there, otherwise onefetch is crashing

I think the easiest thing to do would be to just wrap them all in Option to make them nullable.

Also, ideally, let's not remove options from the CLI. Here's roughly how shared options should be resolved.

let opt = cli.opt.or(config.opt).unwrap_or(default_opt);

Check out the huge amount of helper methods defined on Option<T>.

@Sk7Str1p3
Copy link
Contributor Author

Also, ideally, let's not remove options from the CLI. Here's roughly how shared options should be resolved.

i dont really think someone would like to override such things like separators from CLI

by the way, check latest commit, i did wrap values on Option, but code seems kinda idiotic imo

@Sk7Str1p3
Copy link
Contributor Author

image
why does this freaky ':' appears?

@Sk7Str1p3
Copy link
Contributor Author

Sk7Str1p3 commented Apr 1, 2025

@spenserblack i could not really find why ":" is hardcoded as separator, do you have any ideas?

@Sk7Str1p3
Copy link
Contributor Author

It seems to compile, finally

@Sk7Str1p3
Copy link
Contributor Author

Lmfao it thinks config.toml is a directory. I forgot to use parent() method, I will fix tomorrow

@Sk7Str1p3
Copy link
Contributor Author

@spenserblack I experienced some troubles with writing config - the default path is EMPTY. I set it in default() for ConfigCliOptionsbut then i use unwrap_or_default(), path presents empty.
Second issue is that config is being overwritten, I thought fs::write would tell me if file exist

@spenserblack
Copy link
Collaborator

@spenserblack I experienced some troubles with writing config - the default path is EMPTY. I set it in default() for ConfigCliOptionsbut then i use unwrap_or_default(), path presents empty.
Second issue is that config is being overwritten, I thought fs::write would tell me if file exist

Yeah, you're going to have to combine it with dirs. The home/config directory is kind of an edge case, so it might be OK to have a panicking unwrap when dirs returns None

@Sk7Str1p3
Copy link
Contributor Author

hm @spenserblack something odd happens. it cant unwrap to default because the value is PRESENT, but it is an empty path. i cannot figure out the cause of this problem

src/config.rs Outdated
}
}

pub fn write_default<P>(path: P) -> Result<()>
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you refactor this to write(&self, path), then we can call ::default().write("path") instead. A bonus from this is that we're already set up to write other configs, for example if we needed to dump the config for debugging.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done;

i also think you found my commits chaotic and weird, i will recommit in more pretty way a bit later

@spenserblack
Copy link
Collaborator

hm @spenserblack something odd happens. it cant unwrap to default because the value is PRESENT, but it is an empty path. i cannot figure out the cause of this problem

🤔 I'll have to look into where that's happening, but Option::<T>::default() should always be Option::None.

src/main.rs Outdated
if cli_options.config.generate_config {
// what the actual FUCK is happening here?
// why default path is EMPTY?
return ConfigOptions::write_default(&cli_options.config.config_path.unwrap_or_default());
Copy link
Collaborator

@spenserblack spenserblack May 5, 2025

Choose a reason for hiding this comment

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

At, here it is, if I understand your problem correctly. Don't do .unwrap_or_default(), do .unwrap_or_else(|| dirs::config_path().expect("config path to exist").

Edit: actually, write(), rather than using expect(), you can return a Result::Err.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Option::<PathBuf>::unwrap_or_default() is equivalent to

let path_buf = match config_path {
    Some(p) => p,
    None => PathBuf::default(),
};

I'm thinking you were expecting the default PathBuf as defined in your Default implementation, instead of the PathBuf::default().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

exactly what i expected to get, is it supposed to work like that or it is a bug?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm pretty sure PathBuf::default() is supposed to return an empty PathBuf, if that's what you're asking.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I mean why does unwrap_or_defaultreturn PathBuf::default()and not ConfigCliOptions::default().config_path

src/info/mod.rs Outdated
Self {
title: None,
info_fields: Vec::new(),
// I have totally NO idea how to properly override values
// if cli_options.info.disabled_fields is NOT type of Option<T>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@spenserblack not sure if i should wrap all types into Option <T> as i suppose this would require major changes in code but i cant see another way to override values

Copy link
Collaborator

Choose a reason for hiding this comment

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

Check out these docs for telling serde what the value should default to if it's not available: https://serde.rs/attr-default.html

That way you don't have to wrap everything in Option and manually unwrap to a default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

isn't this should be done with clap rather than serde? we are overriding config with cli, so rather clap should default to config value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, yes, clap also provide default_value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tested in last commit. Currently I got vectors concatenation instead of overwrite, and clap throws error if --disabled-fields is not set with CLI. I think wrap into Option would be better decision

@Sk7Str1p3
Copy link
Contributor Author

@spenserblack I tried to implement parameters override but without wrapping into Option this is near to impossible or at least much more complicated than it could be

This reverts commit c4fdd83.
@Sk7Str1p3
Copy link
Contributor Author

Sk7Str1p3 commented May 6, 2025

Yep as I expected, wrapping into Option is most convenient, logical, right way to override. Currently im trying to write a function which would look like

let disabled_fields = merge_with_cli(/* Value to merge */)
// or maybe CliOptions::merge? 

Tho I also must decide whether to merge entire struct or just one value...

Anyway I'm on final stretch and this PR is going to be ready to get merged really soon

@Sk7Str1p3 Sk7Str1p3 requested a review from spenserblack May 7, 2025 19:07
@Sk7Str1p3 Sk7Str1p3 marked this pull request as ready for review May 7, 2025 19:07
@Sk7Str1p3
Copy link
Contributor Author

@o2sh ready to merge (just will have to recommit in prettier way), and I would like to get some advices on my code

Copy link

@SirJiga SirJiga left a comment

Choose a reason for hiding this comment

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

Nothing interesting

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.

Config file for theming/customization💄
5 participants