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

API Design #12

Open
3 of 11 tasks
Freyskeyd opened this issue Oct 17, 2017 · 7 comments
Open
3 of 11 tasks

API Design #12

Freyskeyd opened this issue Oct 17, 2017 · 7 comments

Comments

@Freyskeyd
Copy link
Owner

Freyskeyd commented Oct 17, 2017

Hello,

This issue will be the place to discuss about API Design.

The goal of Environment

Environment is a crate to help users on creating/overriding/remove environments variables.

It doesn't directly alter the current thread environment but offer a way to generate a representation of a potential environments.

Features

The user must be able to:

  • Create an empty environment object
  • Create an inherited environment object
  • Update the value of a variable
  • Clear the value of a variable
  • Remove a variable
  • Conditionally apply any of the previously defined actions
  • Convert the environment object as an iterator
  • Create an environment from an Iterator
  • Compare an Environment with another Environment object
  • Compare an Environment with an Iterator
  • Compare a variable's value to a str/string/OsString

Feel free to add more feature here in comments

API Design

Instantiate Objects

You can instantiate an Environment different ways:

// Don't inherit from current thread env
Environment::empty();

// Inherit from current thread env
Environment::inherit();

// Why any type of iterator
Environment::from_iter((0..5).into_iter()
    .map(|x| (format!("KEY_{}", x), x)
);

// With predefined environment variable
Environment::with_var(("foo", "bar"));

// With predefined environment variable
Environment::with_vars(&[("foo", "bar")]);

// Empty environment (macro style)
empty_env!{};

// Inherited environment (macro style)
inherit_env!{};

// Empty environment with_env(macro style)
empty_env!{
     "foo" => "bar"
     "debug" = "true"
};

let env: Environment = vec![("foo", "bar"), ("debug", "true")].into();

To instantiate a variable:

// Created from a tuple
let var: Var = ("foo", "bar").into();

// Created from a slice
let var: Var = &["foo", "bar"].into();

// Created with a key name (Question: must inherit or not?)
Var::new("foo");

// Created from a tuple
Var::from(("foo", "bar"));

// Created from a slice
Var::from(&["foo", "bar"]);

// Created with a key name and inherit
Var::inherit("foo");

Variable manipulations

Here's some variable manipulations:

// Let's assume that `v` is a `Var` instantiate that way `("foo", "bar").into()`
// the `key` is `foo` and the `value` is `bar`.

// Inherit the value from the current thread
v.inherit();

// Override the current value
v.assign("baz");

// Set the current value to an empty string
v.clear();

// Remove the variable from the underlying environment (weird when dealing with only one variable)
v.remove();

// Append a value to the existing one
v.append(";path");

// Condition applied to the next assign
// If the `is` is true, the action is proceed
// If the `isnt` is true, the action is proceed
v.is("bar").assign("baz");
v.isnt("bar").remove();

// `get` return a `to_string_lossy` of the value
assert!(v.get(), "bar");

Integration with Environment object:

To make a fluent API, the Environment object is passed to the Var object

let env = Environment::empty();

// Use the environment object to define two variables (foo and log_level) without condition.
env
    .var("foo").assign("bar")
    .var("log_level").assign("info"); // Environment: [(foo, bar), (log_level, info)]

let env = Environment::with_var(("foo", "bar"));

// The environment stay the same if the condition is not valid
env.var("foo").isnt("bar").assign("baz"); // Environment: [(foo, bar)]

env                  // Environment Object
    .var("foo")      // Var Object
    .assign("bar");  // Environment Object

Contribute

You can contribue to this PR just by commenting it.

@Freyskeyd
Copy link
Owner Author

cc @epage @killercup

@epage
Copy link

epage commented Oct 17, 2017

While I understand the assert_cli use cases, could you describe the other use cases so I better understand the requirements and what direction you're wanting to take this?

Some questions that spawn from this

  • What is the use case for directly creating a variable?

Misc thoughts

  • What about a fluent means of inheriting a variable?
  • What happens to the value in an Environment when a condition fails?
  • How much value do the macros provide? What about encouraging things like literate's macros
// Why any type of iterator
Environment::from_iter((0..5).into_iter()
    .map(|x| (format!("KEY_{}", x), x)
);

I feel the value comes from idiomatic interop with other types.

@Freyskeyd
Copy link
Owner Author

The main use case is about tests environments. But I also use Environment for a CLI tools that spawn other command on differents machines.

What is the use case for directly creating a variable?

To clone it in different Environment object. But in fact it's a corner case I think.

What about a fluent means of inheriting a variable?
You're talking about inherit method on a Var right?

Like:

env.var("foo").inherit();

or

let var = ("foo", "").into();
var.inherit();

I'm wondering, when accessing to a var on an environment.The inherit method must lookup only in the var_os ?

What happens to the value in an Environment when a condition fails?

When a condition fails on a particular key, the state stay the same. (Maybe I'm missing something on this question)

How much value do the macros provide?

The macro style is, maybe, more visual. But in fact I don't know if it's really necessary. Maybe not.

@epage
Copy link

epage commented Oct 20, 2017

The main use case is about tests environments. But I also use Environment for a CLI tools that spawn other command on differents machines.

Cool, sounds like a fun project :)

What about a fluent means of inheriting a variable?

You're talking about inherit method on a Var right?
env.var("foo").inherit();

Yes. I brought it up because you only had Var::inherit("foo"); in your post.

I expect I'll rarely, if ever, directly construct a Var but instead access them off of an Environment. Not sure if thats the same for you and others.

I'm wondering, when accessing to a var on an environment.The inherit method must lookup only in the var_os ?

I feel I'm missing something here. Let me take a guess at answering.

I like your definition of inherit in your PR

When a condition fails on a particular key, the state stay the same. (Maybe I'm missing something on this question)

I was bringing it up because of this discussion

The macro style is, maybe, more visual. But in fact I don't know if it's really necessary. Maybe not.

To be clear, I have no strong preference on this. I just figured it'd be nice if we standardized on pointing people to common, reusable patterns rather than every library reinventing these kinds of macros.

@Freyskeyd
Copy link
Owner Author

@epage

I agree with the missing inherit on Var I forgot it.

About inherit let me show you a possible case:

::std::env::set_var("foo", "testing");
let env = Environment::inherit()
    .var("foo").assign("bar")
    .var("foo").isnt("baz").inherit();

In this example, at the end of the chaining, do you want to have foo equal to bar or testing?

@epage
Copy link

epage commented Oct 23, 2017

I'm wondering, when accessing to a var on an environment.The inherit method must lookup only in the var_os ?

About inherit let me show you a possible case:

  1. foo is set to testing outside of Environment
  2. foo is assigned to bar inside of Environment
  3. Condition passes, causing inherit to run.

I'd probably classify that as a corner case and because of that my preference on behavior would be defined by what I think the primary case is.

// Whitelisting environment variables to inherit
let env = Environment::empty().var("foo").inherit();

// Conditionally whitelisting environment variables to inherit
::std::env::set_var("foo", "testing");
let env = Environment::empty().var("foo").isnt("baz").inherit();

To make these cases successful, inherit cannot read from Environment but must read from std::env.

Therefore we should expect inherit to always read from std::env to be less surprising and easier to document.

So

::std::env::set_var("foo", "testing");
let env = Environment::inherit()
    .var("foo").assign("bar")
    .var("foo").isnt("baz").inherit();

foot=testing

Another angle to look at this discussion is that .var().inherit() should pull from std::env because the it sounds circular for it to inherit from the Environment its operating on.

@Freyskeyd
Copy link
Owner Author

Ok it's great and clear.

So any call to inherit will use the std::env::vars_os (or var) to update the value.

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

No branches or pull requests

2 participants