{"id":13682856,"url":"https://github.com/dgrisham/wenv","last_synced_at":"2025-04-14T10:21:40.428Z","repository":{"id":29306796,"uuid":"121312589","full_name":"dgrisham/wenv","owner":"dgrisham","description":"a personal workflow project gone mad","archived":false,"fork":false,"pushed_at":"2025-04-11T15:23:15.000Z","size":292,"stargazers_count":32,"open_issues_count":0,"forks_count":3,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-11T16:40:56.252Z","etag":null,"topics":["arch","bash","cli","extensions","freebsd","linux","osx","shell","terminal","tmux","workflow","zsh"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dgrisham.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-02-12T22:50:39.000Z","updated_at":"2025-04-11T15:23:19.000Z","dependencies_parsed_at":"2023-02-13T00:45:27.199Z","dependency_job_id":"af0f2945-fd58-4b85-8883-b134fdf5598b","html_url":"https://github.com/dgrisham/wenv","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrisham%2Fwenv","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrisham%2Fwenv/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrisham%2Fwenv/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrisham%2Fwenv/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dgrisham","download_url":"https://codeload.github.com/dgrisham/wenv/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248860007,"owners_count":21173344,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arch","bash","cli","extensions","freebsd","linux","osx","shell","terminal","tmux","workflow","zsh"],"created_at":"2024-08-02T13:01:54.406Z","updated_at":"2025-04-14T10:21:40.417Z","avatar_url":"https://github.com/dgrisham.png","language":"Shell","funding_links":[],"categories":["Shell"],"sub_categories":[],"readme":".. default-role:: literal\n.. sectnum::\n\nwenv: A Shell Workflow Tool\n===========================\n\n.. contents:: Table of Contents\n\nIntroduction\n------------\n\nWorking in the terminal is fun, but it can also be tedious and messy. The working\nenvironment (wenv) project offloads the tedious work required to provide clean,\nproject-specific environments that allow users to easily leverage the power of\ntheir shell. It acts as an extremely lightweight layer that connects tmux, Zsh,\nand your other favorite shell tools.\n\nThe Story\n~~~~~~~~~\n\nMy (@dgrisham's) motivation for this project came from a desire to make working\nin the terminal clean, easy, and fun. Zsh and tmux were ubiquitous in my\nworkflow, and over time I started to see potential in those tools that I wanted\nto take advantage of. The wenv framework emerged from this potential. The actual\nconception of this project formed as I tried to solve relatively disparate\nproblems. I'll start by describing some of those problems, then how they came\ntogether to shape a single solution.\n\nOne of the issues I faced had to do with starting tmux with a given layout. Every\ntime I started to work, I'd have to go through the steps of setting up the\nappropriate layout of panes and opening the relevant programs in those panes.\nThis seemed silly, given that I was working in the terminal and should be able to\nautomate such things. I saw advice like `this answer on Stack Overflow\n\u003chttps://stackoverflow.com/a/5752901/4516052\u003e`_, which recommends running a\nsequence of tmux commands as a function that starts your desired development\nenvironment. However, as I worked on projects, I found that I'd want different\ntmux layouts depending on the project. Further, I wanted more than just a tmux\nlayout -- I also wanted to automatically run project-specific commands in certain\nterminals in a given layout. This would require a bit more work than the shell\nfunction the SO post.\n\nAt the same time, I'd set up a simple system for managing aliases with different\ndomains. For example, I had a Python aliases file that defined useful functions\nfor Python development (mainly creating and starting virtual environments). I\nalso wrote alias files specific to certain projects, e.g. the `IPTB\n\u003chttps://github.com/ipfs/iptb\u003e`_ development aliases defined the required\n`IPTB_ROOT` environment variable and additional functions to simplify IPTB and\nDocker startup, shutdown, and cleanup. Then I just had a few Zsh commands that\nwould let me easily create, edit, and source these alias files. Organizing\naliases in this way avoids putting a bunch of temporary or overly-specific\naliases in my general Zsh aliases file, which I'd rather be accessible and\ngeneral enough for people who might want to draw from it.\n\nEventually, I decided to integrate a solution to my varied-tmux-startup conundrum\nwith the alias files. That way, the entire definition for a project (both its\nlayout and its Zsh variables/functions) could exist in one place. This approach\nalso presented the possibility of having a project's aliases defined in every\ntmux pane/window in a given session, which would\n\n1.  be much nicer than having to manually source the aliases in every pane that\n    I needed them, while\n2.  maintaining a clean Zsh namespace by restricting the project's aliases to a\n    single tmux session.\n\nThis emerging idea turned out to be more involved than I'd expected. Sourcing a\ngiven aliases file in every pane of a tmux session required work on both the Zsh\nend (to tell tmux which aliases file to source) and the tmux end (to actually\nsource the aliases in each pane).\n\nOther useful features also quickly became apparent as I started working on this\napproach. For example, developing the aforementioned IPTB project required a\nrunning Docker daemon. I didn't want Docker to run unless I was working on the\nproject, but I didn't want to have to think about starting processes like that\nwhen I was about to work. So, I thought it'd be nice if I could include something\nin the IPTB project file that would let me automatically run commands like `sudo systemctl start docker`\nwhen I started working on the project and `sudo systemctl stop docker` when I was finished.\n\nThe wenv framework arose from this increasing complexity. However, it never left\nthe realm of Zsh scripting. A project's wenv is defined by Zsh environment\nvariables and functions, and the wenv 'framework' is just a bunch of Zsh\nfunctions. This is convenient because much of the code is just sequences of\ncommands I'd run in the terminal anyway, and the rest maintain state in the Zsh\nand tmux environments.\n\nThe advantages that stem from this tool being 'lightweight' go beyond the usual\nideas of load time/etc. The art of shell scripting is slowly being lost to more\nintegrated and high-level tools. While those tools might have their place\n(debatable), there are disadvantages to the lower resolution [*]_. The lightweight\nnature of the wenv project aims to achieve many of the advantages that higher level\ntools offer while staying at the level of abstraction of the shell. This means that\nwenvs require minimal understand on the user's part beyond shell scripting -- if\nyou understand how shells work, then you're only a few steps from understanding\nhow wenvs work. And if you don't understand how shells work, using wenvs can help\nyou do so.\n\n.. [*] For more on this topic, see the talk `Preventing the Collapse of Civilation\n   by Jonathon Blow \u003chttps://www.youtube.com/watch?v=pW-SOdj4Kkk\u003e`_.\n\nInstallation\n------------\n\nDependencies\n~~~~~~~~~~~~\n\n-   Zsh\n-   tmux\n\nSteps\n~~~~~\n\nFor now, the installation is manual -- fortunately, it's also relatively\npainless. The following steps (or variations on them) should get the job done\n(note that you may want to store this repo somewhere permanent and symlink to\nits contents instead of copying to make updates easier):\n\n1.  Clone this repository.\n2.  Create the directory `$XDG_CONFIG_HOME/wenv` (or `$HOME/.config/wenv`) and\n    put both the `template` file and `extensions` directory there. Also, create\n    a directory inside of that `wenv` directory called `wenvs`, which will store\n    the wenv files for all of your projects. If you're in this repository, you\n    can run the following lines to complete this step:\n\n    .. code-block:: zsh\n\n        export wenv_cfg=\"${XDG_CONFIG_HOME:-$HOME/.config}/wenv\"\n        mkdir -p \"$wenv_cfg/wenvs\"\n        ln -s \u003cpath-to-this-repo\u003e/{template,extensions} \"$wenv_cfg\"\n\n3.  Put the `wenv` and `_wenv` files wherever you like, and add the following lines to your `zshrc`:\n\n    .. code-block:: zsh\n\n        # source wenv file\n        source \u003cpath-to-wenv-file\u003e\n\n4.  To load the completions, you can move or symlink the `_wenv` file to a directory in your `fpath`.\n    For example, if the completion file is at `~/src/wenv/_wenv` and you store completions in\n    `$XDG_DATA_HOME/zsh/completions/`, you would run:\n\n    .. code-block:: zsh\n\n        ln -s `~/src/wenv/_wenv` `$XDG_DATA_HOME/zsh/completions/`\n\n    Then ensure the path is in your `fpath` by adding this to your `zshrc`:\n\n    .. code-block:: zsh\n\n        fpath=($XDG_DATA_HOME/zsh/completions $fpath)\n\n5.  In order for wenvs to work with `tmux`, the following line should be added\n    to your `zshrc`:\n\n    .. code-block:: zsh\n\n        [[ -n \"$WENV\" ]] \u0026\u0026 wenv_source -c \"$WENV\"\n\n    This makes it so that the wenv associated with a given tmux session can be\n    loaded whenever a new pane or window is opened within that session.\n\nRecommended\n~~~~~~~~~~~\n\n**Wenv name in prompt**\n\nIt's useful to have the name of the wenv in your prompt, as both an easy reference for which wenv you're in and\nsometimes as a debugging tool to verify whether a wenv properly loaded. This used to be the default, but for better\nflexibility it's now up to the user to configure this.\n\nA simple way to do this would be to add the following lines to your `zshrc`:\n\n.. code-block:: zsh\n\n    wenv_prompt() {\n        [[ -n \"$WENV\" ]] \u0026\u0026 echo \"($WENV) \"\n    }\n\n    setopt prompt_subst\n    PS1=\"\\$(wenv_prompt)$PS1\"\n\nThis prepends the name of the active wenv in parentheses, followed by a space, before your prompt.  This may be\nadded before or after the code added in step 4.  For more information on the `prompt_subst` option in Zsh, see\nhttps://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html.\n\n**Clean wenv startup history**\n\nWhen you run the `wenv start` command, you'll get the following command in your shell's history:\n\n.. code-block:: zsh\n\n    source $tmp_start_file \u0026\u0026 rm -f $tmp_start_file\n\nThis command is prefixed with space -- this means that if you have the `HIST_IGNORE_SPACE` Zsh option set, that command\nwon't be saved in your shell history. To set this option, add the following to your `zshrc`:\n\n.. code-block:: zsh\n\n    setopt HIST_IGNORE_SPACE\n\nUsage\n-----\n\n::\n\n    USAGE\n      wenv [-h] \u003ccmd\u003e\n\n    OPTIONS\n      -h                    Display this help message.\n\n    SUBCOMMANDS\n      start \u003cwenv\u003e          Start the working environment \u003cwenv\u003e.\n      stop                  Stop the current working environment.\n      new                   Create a new working environment.\n      list                  List all wenvs\n      edit \u003cwenv\u003e           Edit the wenv file for \u003cwenv\u003e.\n      rename \u003cold\u003e \u003cnew\u003e    Rename wenv \u003cold\u003e to \u003cnew\u003e.\n      remove \u003cwenv\u003e         Delete the wenv file for \u003cwenv\u003e.\n      source \u003cwenv\u003e         Source the wenv file for \u003cwenv\u003e.\n      cd \u003cwenv\u003e             Change to \u003cwenv\u003e's base directory.\n      extension \u003ccmd\u003e       Interact with wenv extensions.\n      bootstrap \u003cwenv\u003e      Run \u003cwenv\u003e's bootstrap function.\n\n    Run `wenv \u003ccmd\u003e -h` for more information on a given subcommand \u003ccmd\u003e.\n\nWenv Environment Summary\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nSee the Walkthrough_ for further elaboration and examples.\n\n**Variables**\n\n-  `wenv_dir`: The path to the base directory of this project.\n-  `wenv_deps`: An array containing the names of the wenvs that this wenv is\n   dependent on.\n-  `wenv_extensions`: An array containing the names of the extensions to load\n   for the wenv.\n\n**Functions**\n\n-   `startup_wenv()` is run whenever you start the wenv. This function is good\n    for starting up any necessary daemons, setting up a tmux layout, opening\n    programs (e.g. a text editor), etc. It will run inside `\"$wenv_dir\"`.\n-   `shutdown_wenv()` is run when you stop the wenv. This can be used to stop\n    daemons started by `startup_wenv()`, and do any other cleanup.\n-   `bootstrap_wenv()` sets up the environment that the wenv expects to exist.\n    For example, this function might pull down a git repository for development\n    or check to ensure that all packages required by this wenv are installed.\n    You can run this function on a wenv `\u003cwenv\u003e` by running\n    `wenv bootstrap \u003cwenv\u003e`.\n\nWalkthrough\n-----------\n\nThe utility of wenvs takes a bit of time to explain. This walkthrough gives the\nbasic configuration/commands for getting started while also explaining what I've\nfound them to be useful for. If you're experienced with shell scripting, you'll\nsee that much of the value of wenvs comes from allowing the user to leverage the\ntools provided by shells. This project is less focused on forcing a specific\nworkflow for users and more focused on giving users a convenient environment in\nwhich to define their own workflow unrestricted by the limitations of a single\nterminal.\n\nThe example wenvs in the `examples`__ directory give concrete examples of wenv\ndefinitions for general projects. Each example includes a comprehensive\ndescription of the wenv's definition and features that are used to create a clean\nand useful environment. I recommend going through these examples, as they\ncompliment this walkthrough.\n\n__ examples/\n\nCreating a wenv\n~~~~~~~~~~~~~~~\n\nHere's an example that creates a wenv for a project called 'hello-world':\n\n.. code-block:: zsh\n\n    $ mkdir hello-world\n    $ cd hello-world\n    $ wenv new hello-world\n\nThe `wenv new` command will copy the wenv `template` file into a new wenv\nfile called `hello-world`. The template file provides a base structure for a new\nwenv. On my machine, the above wenv command creates a new wenv file that starts\nwith the following lines:\n\n.. code-block:: zsh\n\n    wenv_dir=\"\"\n    wenv_deps=()\n    wenv_extensions=('wd')\n\n    startup_wenv() {}\n    shutdown_wenv() {}\n    bootstrap_wenv() {}\n\n    ((only_load_wenv_vars == 1)) \u0026\u0026 return 0\n\n    # define all desired aliases/functions/etc. here\n\nEach project's wenv start with a set of variables and functions that make it a wenv,\nwhich are all of the variables and functions defined above. Below the above block\nis where any shell aliases/functions/etc. for the project should be defined.\n\nA shell running a wenv will have environment variables exported that correspond to the\nvariables defined in the wenv file:\n\n-   `WENV_DIR` will contain the value of `wenv_dir`.\n-   `WENV_DEPS` will contain the value of `wenv_deps`.\n-   `WENV_EXTENSIONS` will contain the value of `wenv_extensions`.\n\nThe most important of these is `wenv_dir`, which we'll focus on first.\n\n`wenv_dir`\n~~~~~~~~~~\n\nThe `wenv_dir` value represents the base directory of the project. When we\nstart a wenv with e.g. `wenv start hello-world`, we'll automatically `cd` into\nthe project's `wenv_dir`. Further, whenever a wenv is active, we can run `wenv\ncd` (without an argument) to `cd` into its base directory from anywhere. If we\nwant to `cd` into an inactive wenv's `wenv_dir`, we can do so by passing the\nwenv name as an argument -- e.g. `wenv cd hello-world`.\n\nIn the example in the previous section, `wenv_dir`'s value was automatically populated\nwith our current working directory (this may be overidden with the `-d` flag).\n\n`startup_wenv()`\n~~~~~~~~~~~~~~~~\n\nNow let's talk about what you can do when starting a wenv. The `startup_wenv()`\nfunction is run whenever you activate a wenv with `wenv start \u003cwenv\u003e`. This can\nbe useful for running startup commands, e.g.\n\n.. code-block:: zsh\n\n    startup_wenv() {\n        sudo systemctl start docker\n    }\n\nOr opening programs like text editors:\n\n.. code-block:: zsh\n\n    startup_wenv() {\n        $EDITOR main.cpp\n    }\n\nAdditionally the `startup_wenv()` function can be used to automatically create\nTmux layouts for the project.\n\nSo, we can start our wenv with a horizontal split with the startup function:\n\n.. code-block:: zsh\n\n    startup_wenv() {\n        tmux split -h\n    }\n\nWe can also open a file in our text editor in the new pane:\n\n.. code-block:: zsh\n\n    startup_wenv() {\n        tmux split -h \"$EDITOR main.cpp\"\n    }\n\nOther tmux commands can be useful in specifying a layout as well. For example, if\nwe wanted to create a small vertical pane under the initial pane then refocus\non the larger pane:\n\n.. code-block:: zsh\n\n    startup_wenv() {\n        tmux split\n        tmux resize-pane -y 7\n        tmux select-pane -U\n    }\n\nNote that `wenv start` will `cd` into `\"$wenv_dir\"` before\n`startup_wenv()` is run, so you can assume you'll be in the wenv's base\ndirectory when writing your `startup_wenv()` functions. Additionally, your wenv\naliases will be sourced once `startup_wenv()` is called, so can take advantage\nof any environment variables/functions defined outside of `wenv_def()`.\n\n`shutdown_wenv()`\n~~~~~~~~~~~~~~~~\n\nThis is essentially the opposite of `startup_wenv()` -- it runs whenver you\ndeactivate the current wenv with `wenv stop`. So, if we have a wenv whose\n`startup_wenv()` function runs `sudo systemctl start docker`, our\n`shutdown_wenv()` might be:\n\n.. code-block:: zsh\n\n    shutdown_wenv() {\n        sudo systemctl stop docker\n    }\n\nNote, however, that the `wenv stop` command doesn't deactivate the wenv if\n`shutdown_wenv()` returns a non-zero exit code. You can always pass the `-f`\nflag to `wenv stop` to close the wenv even if `shutdown_wenv()` fails.\n\n`wenv_deps`\n~~~~~~~~~~~\n\n`wenv_deps` is an array of wenvs that this wenv is dependent on. Essentially,\nevery wenv in `wenv_deps` is sourced when starting the wenv. Let's take the\nexample of a wenv for IPTB (which we'll call `iptb`):\n\n.. code-block:: zsh\n\n    # ...\n\n    export IPTB_ROOT=\"$HOME/.iptb\"\n\nLet's say we wanted to create another wenv that also used IPTB, and therefore\nalso needs to set the `IPTB_ROOT` variable. We *could* initialize the new wenv\nwith the `iptb` wenv as a base using `wenv new -i iptb \u003cnew_wenv\u003e`, so our new\nwenv would have the same `export` command. However, this approach isn't\nparticularly maintainable -- e.g. if the IPTB developers decide to rename the\n`IPTB_ROOT` variable, all wenvs that use IPTB would have to update that\nvariable's value. Alternatively, we could just source the `iptb` wenv and get\nall of its environment variables every time we start any wenv that uses IPTB. To\ndo this, we'd add `iptb` to our `wenv_deps`:\n\n.. code-block:: zsh\n\n    wenv_deps=('iptb')\n\nExtensions\n~~~~~~~~~~\n\nWenv extensions define shell code that may be reused across multiple wenvs. A\nwenv extension is nothing more than a shell file that you want to source in every\nshell of a wenv. Extensions are stored in `\"$WENV_CFG/extensions\"`. To load an\nextension, add its name to the `wenv_extensions` array. For example, if we\nwanted to load the `wd` and `edit` extensions, we'd write:\n\n.. code-block:: zsh\n\n    wenv_extensions=('wd' 'edit')\n\nThen the files `\"$WENV_CFG/extensions/wd\"` and `\"$WENV_CFG/extensions/edit\"` would\nbe sourced in every shell of our wenv. See the `wd` and `edit` extension files for more\ninformation on their usage.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgrisham%2Fwenv","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdgrisham%2Fwenv","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgrisham%2Fwenv/lists"}