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

Add ability to query modify process environment #11

Open
joshgoebel opened this issue Sep 11, 2021 · 14 comments
Open

Add ability to query modify process environment #11

joshgoebel opened this issue Sep 11, 2021 · 14 comments

Comments

@joshgoebel
Copy link
Owner

joshgoebel commented Sep 11, 2021

Well, before we can do anything useful with the environment we need ENV capabilities in the CLI, which currently do not exist, we likely need to wrap:

  • uv_os_environ
  • uv_os_unsetenv
  • uv_os_setenv
  • uv_os_getenv

Perhaps:

Process.env.all
Process.env["PATH"] 
Process.env["PATH"] = "/usr/bin/"
Process.env.get("PATH")
Process.env.set("PATH", "/usr/bin/")
Process.env.unset("PATH")

Thats likely first a todo/discussion for Wren CLI and Wren Console, as I think environment should be part of the CLI core library...

Originally posted by @joshgoebel in joshgoebel/wren-essentials#7 (comment)

@joshgoebel joshgoebel transferred this issue from joshgoebel/wren-essentials Sep 11, 2021
@joshgoebel joshgoebel changed the title Well, before we can do anything useful with the environment we need ENV capabilities in the CLI, which currently do not exist, we likely need to wrap: Add ability to query modify process environment Sep 11, 2021
@clsource
Copy link

clsource commented Sep 11, 2021

I feel that this would be better to be on its own class inside the os module.

import "os" for Env
class EnvBridge {
  static foreign f_environ
  static foreign f_setenv(key, value)
  static foreign f_unsetenv(key)
  static foreign f_getenv(key)
}

class EnvValue {
  construct new(key, value) {
   _key = key
   _value = value
  }

  key { _key}
  value {_value}
  value(val) =  {
      EnvBridge.f_setenv(key, val)
      _value = val
  }
}

class EnvBag {
  construct new(items) {
       _items = {}
       for(item in items) {
         _items[item.key] = item
      }
  }

  items { _items}
}

class Env {
  static all { EnvBag.new(EnvBridge.f_environ.map { |item| EnvValue.new(item[0], item[1]) }) }
  static get(key) { 
   var item = EnvBridge.f_getenv(key)
   EnvValue.new(item[0], item[1]) 
  }
  static get(key, default) { get(key) || default }
  static set(key, value) { EnvBridge.f_setenv(key, value) }
  static remove(key) { EnvBridge.f_unsetenv(key) }

  static load(content) {
   // parses and loads a dot env string
  }
}

@joshgoebel
Copy link
Owner Author

joshgoebel commented Sep 11, 2021

Overall looks nice.

  • Not sure the Bridge is necessary as a separate class.
  • get(k, default) is a nice touch

static all { EnvBridge.f_environ }

This can't really be so simple though. Likely the raw C function will have to return a Wren List (since there aren't good Map functions on the C side) and then we convert it to a map on the Wren side...

I feel that this would be better to be on its own class inside the os module.

Well, environment is something a process has technically (right?)... still think Process.env might return Env (regardless of where it lands)...

@clsource
Copy link

clsource commented Sep 11, 2021

Not sure the Bridge is necessary as a separate class.

Technically not necessary, is just to avoid mixing foreign internal classes with public userland ones.

Well, environment is something a process has technically (right?)... still think Process.env might return Env (regardless of where it lands)...

class Process {
  static env {Env}
}

@joshgoebel
Copy link
Owner Author

Technically not necessary, is just to avoid mixing foreign internal classes with public userland ones.

I thought that's why we have the f_ prefix and privacy? :-) And also we have the pattern of every other class in the CLI doing it this way... (single class)

@clsource
Copy link

Some Usage Examples

import "os" for Env
// import "os" for Process

// will throw a Fiber.abort if the env is not found
// fetch(key, error)
Env.fetch("USER", "$USER is not set")

// will return clsource if USER is not set
Env.get("USER", "clsource")

// will return null if USER is not set
Env.get("USER")

// will return clsource if the USER is not set. Also will set the value to clsource if not previously set
Env.getOrSet("USER", "clsource")

// will set the value for the key in the Env
Env.set(key, value)

// will delete the key from the env
Env.remove(key)

// Will return all the environment values as a Map
Env.all

// Will return all the environment values as a EnvValue map
Env.values

@joshgoebel
Copy link
Owner Author

  • Why should get return an EnvValue vs just the string value?
  • Do you have values (missing?) and all confused in your code sample?

@clsource
Copy link

clsource commented Sep 11, 2021

  • The all would return the raw key,value map
  • The values would return the EnvValue.

EnvValue is a class that would help you do this

// will set the env USER to clsource
Env.values["USER"] = "clsource"

By using all is for getting the string value only, setting in all would only affect the map, not the env.

Why should get return an EnvValue vs just the string value?

get would return the raw value. If you want the EnvValue you would use the values property

@joshgoebel
Copy link
Owner Author

Why not:

Env["USER"] = "clsource"

@clsource
Copy link

Indeed

class Env {
  static [key] = (value) {
    System.print("%(key): %(value)")
  }
}

Env["USER"] = "clsource"

@joshgoebel
Copy link
Owner Author

Yeah I imagined we'd have [key] and [key]= just for niceness.

@clsource
Copy link

There is this argument https://www.doppler.com/blog/goodbye-env-files that says is better to use json as a storage of env vars instead of dotenv files because json is more standarized. Maybe a function can be added.

load(map)

That will load all envs from a map.

This map can be created using an external parser
either json parser or dotenv parser.

@joshgoebel
Copy link
Owner Author

joshgoebel commented Sep 12, 2021

Of all of this I care the least about the dotenv stuff because to me that feels like it's own thing... really it's just File.load and a loop on top of either JSON or String.split... but at the same time for that reason it also doesn't feel like much scope creep either. So shrugs.

I'd honestly rather support the "standard" though than scope creep into other "good ideas"... if someone wants to load the ENV from JSON vs and env file, that's trivially easy to do on their own.

@clsource
Copy link

I mean is not necessary to include parsing dotenv files. Just include a way to pass a map and then it will load to env.
You can use any way of parsing a file and then include the map to the function.

@joshgoebel
Copy link
Owner Author

Just include a way to pass a map and then it will load to env.

Ah, that sounds a bit more reasonable. :)

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

No branches or pull requests

2 participants