├── .travis.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── desk
├── examples
├── desk.sh
├── hello.fish
├── hello.sh
├── python_project.sh
└── terraform.sh
├── screencap.gif
├── shell_plugins
├── bash
│ └── desk
├── fish
│ └── desk.fish
└── zsh
│ ├── _desk
│ └── desk.plugin.zsh
└── test
├── bashrc
├── run_tests.fish
├── run_tests.sh
└── zshrc
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 |
3 | language: bash
4 |
5 | services:
6 | - docker
7 |
8 | before_install:
9 | - wget http://ftp.debian.org/debian/pool/main/s/shellcheck/shellcheck_0.4.4-4_amd64.deb
10 | - ar x shellcheck_0.4.4-4_amd64.deb
11 | - tar xvf data.tar.xz
12 | - cp ./usr/bin/shellcheck .
13 |
14 | env:
15 | - PATH=$PATH:$(pwd)
16 |
17 | script:
18 | - SHELL_CMD='-c ./run_tests.sh' make bash zsh
19 | - SHELL_CMD='-c ./run_tests.fish' make fish
20 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:14.04
2 | MAINTAINER desk
3 |
4 | ENV USERNAME desktester
5 | RUN adduser --disabled-password --gecos "" desktester
6 | RUN apt-get update && apt-get install -y vim expect zsh fish
7 |
8 | WORKDIR /home/$USERNAME/
9 | ADD desk /usr/local/bin/desk
10 | ADD test/zshrc .zshrc
11 | ADD test/bashrc .bashrc
12 | ADD test/run_tests.sh run_tests.sh
13 | ADD test/run_tests.fish run_tests.fish
14 | ADD examples examples
15 | RUN mkdir -p .config/fish && touch .config/fish/config.fish
16 |
17 | # Set up test Deskfile
18 | RUN mkdir -p example-project && cp examples/hello.sh example-project/Deskfile
19 |
20 | RUN chown -R $USERNAME:$USERNAME .zshrc example-project examples run_tests.fish run_tests.sh .bashrc .config
21 |
22 | USER $USERNAME
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 James O'Beirne
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: install
2 | install:
3 | cp ./desk /usr/local/bin/desk
4 |
5 | .PHONY: uninstall
6 | uninstall:
7 | rm /usr/local/bin/desk
8 |
9 | .PHONY: oh-my-zsh
10 | oh-my-zsh:
11 | ln -s $(shell pwd)/shell_plugins/zsh $(HOME)/.oh-my-zsh/custom/plugins/desk
12 |
13 |
14 | # Test targets
15 | # ------------
16 |
17 | .PHONY: dockerbuild
18 | dockerbuild:
19 | docker build -t desk/desk .
20 |
21 | SHELL_CMD?=
22 |
23 | .PHONY: bash
24 | bash: dockerbuild
25 | docker run -it desk/desk /bin/bash $(SHELL_CMD)
26 |
27 | .PHONY: zsh
28 | zsh: dockerbuild
29 | docker run -it desk/desk /usr/bin/zsh $(SHELL_CMD)
30 |
31 | .PHONY: fish
32 | fish: dockerbuild
33 | docker run -it desk/desk /usr/bin/fish $(SHELL_CMD)
34 |
35 | .PHONY: lint
36 | lint:
37 | shellcheck -e SC2155 desk
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # ◲ desk
3 |
4 | [](https://travis-ci.org/jamesob/desk) [](https://gitter.im/jamesob/desk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
5 |
6 | Lightweight workspace manager for the shell.
7 |
8 | Desk makes it easy to flip back and forth between different project contexts in
9 | your favorite shell. Change directory, activate a virtualenv or rvm, load
10 | in domain-specific aliases, environment variables, functions, arbitrary shell files,
11 | all in a single command.
12 |
13 | Instead of relying on `CTRL-R` to execute and recall ("that command's gotta
14 | be here somewhere..."), desk helps shorten and document those actions with
15 | shell aliases and functions, which are then namespaced under a particular
16 | desk.
17 |
18 | Because Deskfiles are just enriched shell scripts, the possibilities are
19 | endless. For example, when doing work on AWS I have desk
20 | securely load AWS API keys into environment variables via
21 | [`pass`](https://www.passwordstore.org/) -- no effort on my part, and no
22 | risk of accidentally persisting that sensitive information to a history file.
23 |
24 |
25 |
26 | I have a hard time calling this a "workspace manager" with a straight
27 | face -- it's basically just a shell script that sources another shell script in a new shell.
28 | But I often find myself working in multiple different code trees simultaneously:
29 | the quick context switches and namespaced commands that desk facilitates
30 | have proven useful.
31 |
32 | There are no dependencies other than `bash`. Desk is explicitly tested with `bash`,
33 | `zsh`, and `fish`.
34 |
35 | ```sh
36 | ◲ desk 0.6.0
37 |
38 | Usage:
39 |
40 | desk
41 | List the current desk and any associated aliases. If no desk
42 | is being used, display available desks.
43 | desk init
44 | Initialize desk configuration.
45 | desk (list|ls)
46 | List all desks along with a description.
47 | desk (.|go) [ [shell-args...]]
48 | Activate a desk. Extra arguments are passed onto shell. If called with
49 | no arguments, look for a Deskfile in the current directory. If not a
50 | recognized desk, try as a path to directory containing a Deskfile.
51 | desk run
52 | Run a command within a desk's environment then exit. Think '$SHELL -c'.
53 | desk edit [desk-name]
54 | Edit (or create) a deskfile with the name specified, otherwise
55 | edit the active deskfile.
56 | desk help
57 | Show this text.
58 | desk version
59 | Show version information.
60 |
61 | Since desk spawns a shell, to deactivate and "pop" out a desk, you
62 | simply need to exit or otherwise end the current shell process.
63 | ```
64 |
65 | For example, given this deskfile (`~/.desk/desks/tf.sh`):
66 | ```sh
67 | # tf.sh
68 | #
69 | # Description: desk for doing work on a terraform-based repository
70 | #
71 |
72 | cd ~/terraform-repo
73 |
74 | # Set up AWS env variables:
75 | set_aws_env() {
76 | export AWS_ACCESS_KEY_ID="$1"
77 | export AWS_SECRET_ACCESS_KEY="$2"
78 | }
79 |
80 | # Run `terraform plan` with proper AWS var config
81 | plan() {
82 | terraform plan -module-depth=-1 \
83 | -var "access_key=${AWS_ACCESS_KEY_ID}" \
84 | -var "secret_key=${AWS_SECRET_ACCESS_KEY}"
85 | }
86 |
87 | # Run `terraform apply` with proper AWS var config
88 | alias apply='terraform apply'
89 | ```
90 |
91 | we'd get
92 |
93 | ```sh
94 | $ desk . tf
95 | $ desk
96 |
97 | tf
98 | desk for doing work on a terraform repo
99 |
100 | set_aws_env Set up AWS env variables:
101 | plan Run `terraform plan` with proper AWS var config
102 | apply Run `terraform apply` with proper AWS var config
103 | ```
104 |
105 | Basically, desk just associates a shell script (`name.sh`) with a name. When
106 | you call `desk . name`, desk drops you into a shell where `name.sh` has been
107 | executed, and then desk extracts out certain comments in `name.sh` for useful
108 | rendering.
109 |
110 | ### Installing
111 |
112 | #### With homebrew
113 |
114 | `brew install desk`
115 |
116 | #### With curl
117 |
118 | Assuming `~/bin` exists and is on the PATH... otherwise, substitute `/usr/local/bin`
119 | and add `sudo` as needed.
120 |
121 | 0. `curl https://raw.githubusercontent.com/jamesob/desk/master/desk > ~/bin/desk`
122 | 0. `chmod +x ~/bin/desk`
123 |
124 | #### With git
125 |
126 | 0. `git clone git@github.com:jamesob/desk.git && cd desk && sudo make install`
127 |
128 | After that, run `desk init` and start adding deskfiles with either `desk edit [deskfile name]`
129 | or by manually adding shell scripts into your deskfiles directory (by default `~/.desk/desks/`).
130 |
131 | ### Enabling shell extensions
132 |
133 | **NB**: Shell extensions are automatically enabled if Desk is installed via Homebrew.
134 |
135 | #### Using bash
136 |
137 | 0. Add `source /path/to/desk/repo/shell_plugins/bash/desk` to your `.bashrc`.
138 |
139 | #### Using fish
140 |
141 | ```fish
142 | mkdir -p ~/.config/fish/completions
143 | cp /path/to/desk/repo/shell_plugins/fish/desk.fish ~/.config/fish/completions
144 | ```
145 |
146 | #### Using zsh
147 |
148 | 0. Add `fpath=(/path/to/desk/repo/shell_plugins/zsh $fpath)` to your `.zshrc`.
149 |
150 |
151 | Optionally, use one of the zsh plugin frameworks mentioned below.
152 |
153 | #### Using [oh-my-zsh](http://ohmyz.sh/)
154 |
155 | 0. `make oh-my-zsh` from within this repo. This sets up a symlink.
156 |
157 | or
158 |
159 | 0. `cd ~/.oh-my-zsh/custom/plugins`
160 | 0. `git clone git@github.com:jamesob/desk.git /tmp/desk && cp -r /tmp/desk/shell_plugins/zsh desk`
161 | 0. Add desk to your plugin list
162 |
163 | #### Using [Antigen](https://github.com/zsh-users/antigen)
164 |
165 | 0. Add `antigen bundle jamesob/desk shell_plugins/zsh` to your `.zshrc`
166 | 0. Open a new terminal window. Antigen will clone the desk repo and add it to your path.
167 |
168 | #### Using [zgen](https://github.com/tarjoilija/zgen)
169 |
170 | 0. Add `zgen load jamesob/desk shell_plugins/zsh` to your `.zshrc` with your other load commands
171 | 0. `rm ~/.zgen/init.zsh`
172 | 0. Start a new shell; zgen will generate a new `init.zsh` and automatically clone the desk repository for you and add it to your path.
173 |
174 | ### Deskfile rules
175 |
176 | Deskfiles are just shell scripts, nothing more, that live in the desk config directory.
177 | Desk does pay attention to certain kinds of comments, though.
178 |
179 | - *description*: you can describe a deskfile by including `# Description: ...`
180 | somewhere in the file.
181 |
182 | - *alias and function docs*: if the line above an alias or function is a
183 | comment, it will be used as documentation.
184 |
185 | ### Deskfiles
186 |
187 | Deskfiles are just shell scripts at the root of a project directory that
188 | adhere to the conventions above. These can be put into version control to
189 | formalize and ease common development tasks like running tests or doing
190 | local builds.
191 |
192 | For example, if we have some directory or repository called `myproject`, if
193 | we create `myproject/Deskfile`, we'll be able to do any of the following:
194 | ```sh
195 | $ desk go /path/to/myproject/
196 | $ desk go /path/to/myproject/Deskfile
197 | myproject/ $ desk go .
198 | myproject/ $ desk go
199 | ```
200 |
201 | ### Sharing deskfiles across computers
202 |
203 | Of course, the desk config directory (by default `~/.desks`) can be a symlink
204 | so that deskfiles can be stored in some centralized place, like Dropbox,
205 | and so shared across many computers.
206 |
207 | ### Using a non-default config location
208 |
209 | By default, desk configuration lives in `~/.desk` (`$DESK_DIR`) and deskfiles
210 | live in `~/.desk/desks` (`$DESK_DESKS_DIR`). If you want to use some other
211 | location, specify as much in `desk init` and then ensure you set `$DESK_DIR`
212 | and/or `$DESK_DESKS_DIR` to match in your shell's rc file.
213 |
214 | ### Shortening invocation
215 |
216 | Typing `desk .` frequently can get old; personally, I like to alias this with
217 | ```sh
218 | alias d.='desk .'
219 | ```
220 | in my shell rc file.
221 |
222 | ### Usage with OS X
223 |
224 | Desk won't work when used strictly with `~/.bash_profile` on OS X's terminal, since
225 | the content of `~/.bash_profile` is only executed on *login*, not shell creation, as
226 | explained [here](http://www.joshstaiger.org/archives/2005/07/bash_profile_vs.html).
227 |
228 | My recommendation is to use `~/.bashrc` as your general-purpose config file, then simply
229 | have `~/.bash_profile` point to it:
230 | ```sh
231 | # ~/.bash_profile
232 |
233 | if [ -f ~/.bashrc ]; then
234 | source ~/.bashrc
235 | fi
236 | ```
237 |
238 | ### Related projects
239 |
240 | - [godesk](https://github.com/hamin/godesk) by @hamin: a desk launcher with fuzzy filtering
241 |
242 |
243 | ### Tips accepted
244 |
245 | 
246 |
247 | BTC: `18ehgMUJBqKc2Eyi6WHiMwHFwA8kobYEhy`
248 |
249 | Half of all tips will be donated to [an organization providing aid to Syrian refugees](http://www.moas.eu/).
250 |
251 | #### Donations made on behalf of tippers
252 |
253 | | date | amount | organization |
254 | | ---- | ------ | ------------ |
255 | | 2015-11-18 | $1.07 | http://moas.eu |
256 | | 2016-11-14 | $21.00 | http://moas.eu |
257 |
--------------------------------------------------------------------------------
/desk:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # vim: set filetype=sh:
3 |
4 | PREFIX="${DESK_DIR:-$HOME/.desk}"
5 | DESKS="${DESK_DESKS_DIR:-$PREFIX/desks}"
6 | DESKFILE_NAME=Deskfile
7 |
8 |
9 | ## Commands
10 |
11 | cmd_version() {
12 | echo "◲ desk 0.6.0"
13 | }
14 |
15 |
16 | cmd_usage() {
17 | cmd_version
18 | echo
19 | cat <<_EOF
20 | Usage:
21 |
22 | $PROGRAM
23 | List the current desk and any associated aliases. If no desk
24 | is being used, display available desks.
25 | $PROGRAM init
26 | Initialize desk configuration.
27 | $PROGRAM (list|ls)
28 | List all desks along with a description.
29 | $PROGRAM (.|go) [ [shell-args...]]
30 | Activate a desk. Extra arguments are passed onto shell. If called with
31 | no arguments, look for a Deskfile in the current directory. If not a
32 | recognized desk, try as a path to directory containing a Deskfile.
33 | $PROGRAM run ''
34 | $PROGRAM run ...
35 | Run a command within a desk's environment then exit. In the first form
36 | shell expansion is performed. In the second form, the argument vector
37 | is executed as is.
38 | $PROGRAM edit [desk-name]
39 | Edit (or create) a deskfile with the name specified, otherwise
40 | edit the active deskfile.
41 | $PROGRAM help
42 | Show this text.
43 | $PROGRAM version
44 | Show version information.
45 |
46 | Since desk spawns a shell, to deactivate and "pop" out a desk, you
47 | simply need to exit or otherwise end the current shell process.
48 | _EOF
49 | }
50 |
51 | cmd_init() {
52 | if [ -d "$PREFIX" ]; then
53 | echo "Desk dir already exists at ${PREFIX}"
54 | exit 1
55 | fi
56 | read -p "Where do you want to store your deskfiles? (default: ${PREFIX}): " \
57 | NEW_PREFIX
58 | [ -z "${NEW_PREFIX}" ] && NEW_PREFIX="$PREFIX"
59 |
60 | if [ ! -d "${NEW_PREFIX}" ]; then
61 | echo "${NEW_PREFIX} doesn't exist, attempting to create."
62 | mkdir -p "$NEW_PREFIX/desks"
63 | fi
64 |
65 | local SHELLTYPE=$(get_running_shell)
66 |
67 | case "${SHELLTYPE}" in
68 | bash) local SHELLRC="${HOME}/.bashrc" ;;
69 | fish) local SHELLRC="${HOME}/.config/fish/config.fish" ;;
70 | zsh) local SHELLRC="${HOME}/.zshrc" ;;
71 | esac
72 |
73 | read -p "Where's your shell rc file? (default: ${SHELLRC}): " \
74 | USER_SHELLRC
75 | [ -z "${USER_SHELLRC}" ] && USER_SHELLRC="$SHELLRC"
76 | if [ ! -f "$USER_SHELLRC" ]; then
77 | echo "${USER_SHELLRC} doesn't exist"
78 | exit 1
79 | fi
80 |
81 | printf "\n# Hook for desk activation\n" >> "$USER_SHELLRC"
82 |
83 | # Since the hook is appended to the rc file, its exit status becomes
84 | # the exit status of `source $USER_SHELLRC` which typically
85 | # indicates if something went wrong. If $DESK_ENV is void, `test`
86 | # sets exit status to 1. That, however, is part of desk's normal
87 | # operation, so we clear exit status after that.
88 | if [ "$SHELLTYPE" == "fish" ]; then
89 | echo "test -n \"\$DESK_ENV\"; and . \"\$DESK_ENV\"; or true" >> "$USER_SHELLRC"
90 | else
91 | echo "[ -n \"\$DESK_ENV\" ] && source \"\$DESK_ENV\" || true" >> "$USER_SHELLRC"
92 | fi
93 |
94 | echo "Done. Start adding desks to ${NEW_PREFIX}/desks!"
95 | }
96 |
97 |
98 | cmd_go() {
99 | # TODESK ($1) may either be
100 | #
101 | # - the name of a desk in $DESKS/
102 | # - a path to a Deskfile
103 | # - a directory containing a Deskfile
104 | # - empty -> `./Deskfile`
105 | #
106 | local TODESK="$1"
107 | local DESKEXT=$(get_deskfile_extension)
108 | local DESKPATH="$(find "${DESKS}/" -name "${TODESK}${DESKEXT}" 2>/dev/null)"
109 |
110 | local POSSIBLE_DESKFILE_DIR="${TODESK%$DESKFILE_NAME}"
111 | if [ -z "$POSSIBLE_DESKFILE_DIR" ]; then
112 | POSSIBLE_DESKFILE_DIR="."
113 | fi
114 |
115 | # If nothing could be found in $DESKS/, check to see if this is a path to
116 | # a Deskfile.
117 | if [[ -z "$DESKPATH" && -d "$POSSIBLE_DESKFILE_DIR" ]]; then
118 | if [ ! -f "${POSSIBLE_DESKFILE_DIR}/${DESKFILE_NAME}" ]; then
119 | echo "No Deskfile found in '${POSSIBLE_DESKFILE_DIR}'"
120 | exit 1
121 | fi
122 |
123 | local REALPATH=$( cd $POSSIBLE_DESKFILE_DIR && pwd )
124 | DESKPATH="${REALPATH}/${DESKFILE_NAME}"
125 | TODESK=$(basename "$REALPATH")
126 | fi
127 |
128 | # Shift desk name so we can forward on all arguments to the shell.
129 | shift;
130 |
131 | if [ -z "$DESKPATH" ]; then
132 | echo "Desk $TODESK (${TODESK}${DESKEXT}) not found in $DESKS"
133 | exit 1
134 | else
135 | local SHELL_EXEC="$(get_running_shell)"
136 | DESK_NAME="${TODESK}" DESK_ENV="${DESKPATH}" exec "${SHELL_EXEC}" "$@"
137 | fi
138 | }
139 |
140 |
141 | cmd_run() {
142 | local TODESK="$1"
143 | shift;
144 | if [ $# -eq 1 ]; then
145 | cmd_go "$TODESK" -ic "$1"
146 | else
147 | cmd_go "$TODESK" -ic '"$@"' -- "$@"
148 | fi
149 | }
150 |
151 |
152 | # Usage: desk list [options]
153 | # Description: List all desks along with a description
154 | # --only-names: List only the names of the desks
155 | # --no-format: Use ' - ' to separate names from descriptions
156 | cmd_list() {
157 | if [ ! -d "${DESKS}/" ]; then
158 | echo "No desk dir! Run 'desk init'."
159 | exit 1
160 | fi
161 | local SHOW_DESCRIPTIONS DESKEXT AUTO_ALIGN name desc len longest out
162 |
163 | while [[ $# -gt 0 ]]; do
164 | case "$1" in
165 | --only-names) SHOW_DESCRIPTIONS=false && AUTO_ALIGN=false ;;
166 | --no-format) AUTO_ALIGN=false ;;
167 | esac
168 | shift
169 | done
170 |
171 | DESKEXT=$(get_deskfile_extension)
172 | out=""
173 | longest=0
174 |
175 | while read -d '' -r f; do
176 | name=$(basename "${f/${DESKEXT}//}")
177 | if [[ "$SHOW_DESCRIPTIONS" = false ]]; then
178 | out+="$name"$'\n'
179 | else
180 | desc=$(echo_description "$f")
181 | out+="$name - $desc"$'\n'
182 | len=${#name}
183 | (( len > longest )) && longest=$len
184 | fi
185 | done < <(find "${DESKS}/" -name "*${DESKEXT}" -print0)
186 | if [[ "$AUTO_ALIGN" != false ]]; then
187 | print_aligned "$out" "$longest"
188 | else
189 | printf "%s" "$out"
190 | fi
191 | }
192 |
193 | # Usage: desk [options]
194 | # Description: List the current desk and any associated aliases. If no desk is being used, display available desks
195 | # --no-format: Use ' - ' to separate alias/export/function names from their descriptions
196 | cmd_current() {
197 | if [ -z "$DESK_ENV" ]; then
198 | printf "No desk activated.\n\n%s" "$(cmd_list)"
199 | exit 1
200 | fi
201 |
202 | local DESKPATH=$DESK_ENV
203 | local CALLABLES=$(get_callables "$DESKPATH")
204 | local AUTO_ALIGN len longest out
205 |
206 | while [[ $# -gt 0 ]]; do
207 | case "$1" in
208 | --no-format) AUTO_ALIGN=false ;;
209 | esac
210 | shift
211 | done
212 | printf "%s - %s\n" "$DESK_NAME" "$(echo_description "$DESKPATH")"
213 |
214 | if [[ -n "$CALLABLES" ]]; then
215 |
216 | longest=0
217 | out=$'\n'
218 | for NAME in $CALLABLES; do
219 | # Last clause in the grep regexp accounts for fish functions.
220 | len=$((${#NAME} + 2))
221 | (( len > longest )) && longest=$len
222 | local DOCLINE=$(
223 | grep -B 1 -E \
224 | "^(alias ${NAME}=|export ${NAME}=|(function )?${NAME}( )?\()|function $NAME" "$DESKPATH" \
225 | | grep "#")
226 |
227 | if [ -z "$DOCLINE" ]; then
228 | out+=" ${NAME}"$'\n'
229 | else
230 | out+=" ${NAME} - ${DOCLINE##\# }"$'\n'
231 | fi
232 | done
233 |
234 | if [[ "$AUTO_ALIGN" != false ]]; then
235 | print_aligned "$out" "$longest"
236 | else
237 | printf "%s" "$out"
238 | fi
239 | fi
240 | }
241 |
242 | cmd_edit() {
243 | if [ $# -eq 0 ]; then
244 | if [ "$DESK_NAME" == "" ]; then
245 | echo "No desk activated."
246 | exit 3
247 | fi
248 | local DESKNAME_TO_EDIT="$DESK_NAME"
249 | elif [ $# -eq 1 ]; then
250 | local DESKNAME_TO_EDIT="$1"
251 | else
252 | echo "Usage: ${PROGRAM} edit [desk-name]"
253 | exit 1
254 | fi
255 |
256 | local DESKEXT=$(get_deskfile_extension)
257 | local EDIT_PATH="${DESKS}/${DESKNAME_TO_EDIT}${DESKEXT}"
258 |
259 | ${EDITOR:-vi} "$EDIT_PATH"
260 | }
261 |
262 | ## Utilities
263 |
264 | FNAME_CHARS='[a-zA-Z0-9_-]'
265 |
266 | # Echo the description of a desk. $1 is the deskfile.
267 | echo_description() {
268 | local descline=$(grep -E "#\s+Description" "$1")
269 | echo "${descline##*Description: }"
270 | }
271 |
272 | # Echo a list of aliases, exports, and functions for a given desk
273 | get_callables() {
274 | local DESKPATH=$1
275 | grep -E "^(alias |export |(function )?${FNAME_CHARS}+ ?\()|function $NAME" "$DESKPATH" \
276 | | sed 's/alias \([^= ]*\)=.*/\1/' \
277 | | sed 's/export \([^= ]*\)=.*/\1/' \
278 | | sed -E "s/(function )?(${FNAME_CHARS}+) ?\(\).*/\2/" \
279 | | sed -E "s/function (${FNAME_CHARS}+).*/\1/"
280 | }
281 |
282 | get_running_shell() {
283 | # Echo the name of the parent shell via procfs, if we have it available.
284 | # Otherwise, try to use ps with bash's parent pid.
285 | if [ -e /proc ]; then
286 | # Get cmdline procfile of the process running this script.
287 | local CMDLINE_FILE="/proc/$(grep PPid /proc/$$/status | cut -f2)/cmdline"
288 |
289 | if [ -f "$CMDLINE_FILE" ]; then
290 | # Strip out any verion that may be attached to the shell executable.
291 | # Strip leading dash for login shells.
292 | local CMDLINE_SHELL=$(sed -r -e 's/\x0.*//' -e 's/^-//' "$CMDLINE_FILE")
293 | fi
294 | else
295 | # Strip leading dash for login shells.
296 | # If the parent process is a login shell, guess bash.
297 | local CMDLINE_SHELL=$(ps -o comm= -p $PPID | tail -1 | sed -e 's/login/bash/' -e 's/^-//')
298 | fi
299 |
300 | if [ ! -z "$CMDLINE_SHELL" ]; then
301 | basename "$CMDLINE_SHELL"
302 | exit
303 | fi
304 |
305 | # Fall back to $SHELL otherwise.
306 | basename "$SHELL"
307 | exit
308 | }
309 |
310 | # Echo the extension of recognized deskfiles (.fish for fish)
311 | get_deskfile_extension() {
312 | if [ "$(get_running_shell)" == "fish" ]; then
313 | echo '.fish'
314 | else
315 | echo '.sh'
316 | fi
317 | }
318 |
319 | # Echo first argument as key/value pairs aligned into two columns; second argument is the longest key
320 | print_aligned() {
321 | local out=$1 longest=$2
322 | printf "%s" "$out" | awk -v padding="$longest" -F' - ' '{
323 | printf "%-*s %s\n", padding, $1, substr($0, index($0, " - ")+2, length($0))
324 | }'
325 | }
326 |
327 |
328 | PROGRAM="${0##*/}"
329 |
330 | case "$1" in
331 | init) shift; cmd_init "$@" ;;
332 | help|--help) shift; cmd_usage "$@" ;;
333 | version|--version) shift; cmd_version "$@" ;;
334 | ls|list) shift; cmd_list "$@" ;;
335 | go|.) shift; cmd_go "$@" ;;
336 | run) shift; cmd_run "$@" ;;
337 | edit) shift; cmd_edit "$@" ;;
338 | *) cmd_current "$@" ;;
339 | esac
340 | exit 0
341 |
--------------------------------------------------------------------------------
/examples/desk.sh:
--------------------------------------------------------------------------------
1 | # Description: the desk I use to work on desk :)
2 |
3 | cd ~/code/desk
4 |
5 | # Run `make lint`
6 | alias lint="make lint"
7 |
8 | # Args: . Checkout a Github PR's branch for local testing.
9 | checkout_pr () {
10 | git fetch origin pull/$1/head:pr-$1 && git checkout pr-$1;
11 | }
12 |
13 | test() {
14 | make lint
15 | SHELL_CMD='-c ./run_tests.sh' make bash zsh
16 | SHELL_CMD='-c ./run_tests.fish' make fish
17 | }
18 |
--------------------------------------------------------------------------------
/examples/hello.fish:
--------------------------------------------------------------------------------
1 | # Description: a simple test for the fish shell.
2 |
3 | # Args: . Say hello to someone.
4 | function say_hello
5 | echo Hello $argv
6 | end
7 |
--------------------------------------------------------------------------------
/examples/hello.sh:
--------------------------------------------------------------------------------
1 | # Description: simple desk that says hello
2 |
3 |
4 | # Say an informal hello.
5 | hi() {
6 | echo "hi, ${1}!"
7 | }
8 |
9 | # Use if you're from Texas.
10 | alias howdy="echo howdy y\'all"
11 |
12 | # Why should I always type my name
13 | export MyName="James"
14 |
15 |
--------------------------------------------------------------------------------
/examples/python_project.sh:
--------------------------------------------------------------------------------
1 | # Description: desk for working on a Python project
2 |
3 | cd ~/python_project
4 |
5 | source venv/bin/activate
6 |
7 | PROJECT_NAME=python_project
8 |
9 | # Run unittests with nose
10 | alias t="nosetests ${PROJECT_NAME}/tests"
11 |
12 | # Install requirements
13 | alias req="pip install -r requirements.txt"
14 |
15 | # Push the package to PyPI
16 | alias pypipush="python setup.py sdist upload -r ${PROJECT_NAME}"
17 |
--------------------------------------------------------------------------------
/examples/terraform.sh:
--------------------------------------------------------------------------------
1 | # Description: desk for doing work on a terraform-based repository
2 | #
3 |
4 | cd ~/terraform-repo
5 |
6 | # Set up AWS env variables:
7 | set_aws_env() {
8 | export AWS_DEFAULT_REGION=us-west-1
9 | export AWS_ACCESS_KEY_ID="$1"
10 | export AWS_SECRET_ACCESS_KEY="$2"
11 | }
12 |
13 | # Run `terraform plan` with proper AWS var config
14 | plan() {
15 | terraform plan -module-depth=-1 \
16 | -var "access_key=${AWS_ACCESS_KEY_ID}" \
17 | -var "secret_key=${AWS_SECRET_ACCESS_KEY}"
18 | }
19 |
20 | # Run `terraform apply` with proper AWS var config
21 | apply () {
22 | terraform apply \
23 | -var "access_key=${AWS_ACCESS_KEY_ID}" \
24 | -var "secret_key=${AWS_SECRET_ACCESS_KEY}"
25 | }
26 |
27 | # Set up terraform config:
28 | config () {
29 | local KEY=$1
30 | terraform remote config -backend=s3 \
31 | -backend-config="bucket=some.bucket.secrets.terraform" \
32 | -backend-config="key=${KEY}"
33 | }
34 |
--------------------------------------------------------------------------------
/screencap.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesob/desk/252e6834b4a4a535fe905c6087e7eecfda70e040/screencap.gif
--------------------------------------------------------------------------------
/shell_plugins/bash/desk:
--------------------------------------------------------------------------------
1 | #Bash completion for ◲ desk
2 |
3 | _desk() {
4 | PREFIX="${DESK_DIR:-$HOME/.desk}"
5 | DESKS="${DESK_DESKS_DIR:-$PREFIX/desks}"
6 |
7 | cur=${COMP_WORDS[COMP_CWORD]}
8 | prev=${COMP_WORDS[COMP_CWORD-1]}
9 |
10 | case ${COMP_CWORD} in
11 | 1)
12 | COMPREPLY=($(compgen -W "edit . go run help init list ls version" ${cur}))
13 | ;;
14 | 2)
15 | case ${prev} in
16 | edit|go|.|run)
17 | if [[ -d $DESKS ]]; then
18 | local desks=$(desk list --only-names)
19 | else
20 | local desks=""
21 | fi
22 | COMPREPLY=( $(compgen -W "${desks}" -- ${cur}) )
23 | ;;
24 | esac
25 | ;;
26 | *)
27 | COMPREPLY=()
28 | ;;
29 | esac
30 | }
31 |
32 |
33 | complete -F _desk desk
34 |
--------------------------------------------------------------------------------
/shell_plugins/fish/desk.fish:
--------------------------------------------------------------------------------
1 | # desk.fish - desk completions for fish shell
2 | #
3 | # To install the completions:
4 | # mkdir -p ~/.config/fish/completions
5 | # cp desk.fish ~/.config/fish/completions
6 |
7 | function __fish_desk_no_subcommand --description 'Test if desk has yet to be given a subcommand'
8 | for i in (commandline -opc)
9 | if contains -- $i init list ls . go run edit help version
10 | return 1
11 | end
12 | end
13 | return 0
14 | end
15 |
16 | # desk
17 | complete -c desk -f -n '__fish_desk_no_subcommand' -a init -d 'Initialize desk configuration'
18 |
19 | # desk list|ls
20 | complete -c desk -f -n '__fish_desk_no_subcommand' -a "ls list" -d 'List all desks along with a description'
21 | complete -c desk -A -f -n '__fish_seen_subcommand_from ls list' -l only-names -d 'List only the names of the desks'
22 | complete -c desk -A -f -n '__fish_seen_subcommand_from ls list' -l no-format -d "Use ' - ' to separate names from descriptions"
23 |
24 | # desk go|.
25 | complete -c desk -f -n '__fish_desk_no_subcommand' -a "go ." -d 'Activate a desk. Extra arguments are passed onto shell'
26 | complete -c desk -A -f -n '__fish_seen_subcommand_from . go' -a "(desk ls --only-names)" -d "Desk"
27 |
28 | # desk run
29 | complete -c desk -f -n '__fish_desk_no_subcommand' -a run -d 'Run a command within a desk\'s environment then exit'
30 | complete -c desk -A -f -n '__fish_seen_subcommand_from run' -a '(desk ls --only-names)' -d "Desk"
31 |
32 | # desk edit
33 | complete -c desk -f -n '__fish_desk_no_subcommand' -a edit -d 'Edit (or create) a deskfile with the name specified, otherwise edit the active deskfile'
34 | complete -c desk -A -f -n '__fish_seen_subcommand_from edit' -a '(desk ls --only-names)' -d "Desk"
35 |
36 | # desk help
37 | complete -c desk -f -n '__fish_desk_no_subcommand' -a help -d 'Show help text'
38 |
39 | # desk version
40 | complete -c desk -f -n '__fish_desk_no_subcommand' -a version -d 'Show version information'
41 |
--------------------------------------------------------------------------------
/shell_plugins/zsh/_desk:
--------------------------------------------------------------------------------
1 | #compdef desk
2 | #autoload
3 |
4 | _all_desks() {
5 | desks=($(desk list --only-names))
6 | }
7 |
8 | local expl
9 | local -a desks
10 |
11 | local -a _subcommands
12 | _subcommands=(
13 | 'help:Print a help message.'
14 | 'init:Initialize your desk configuration.'
15 | 'list:List available desks'
16 | 'ls:List available desks'
17 | 'edit:Edit or create a desk, defaults to current desk'
18 | 'go:Activate a desk'
19 | '.:Activate a desk'
20 | 'run:Run a command within a desk environment'
21 | 'version:Show the desk version.'
22 | )
23 |
24 | if (( CURRENT == 2 )); then
25 | _describe -t commands 'desk subcommand' _subcommands
26 | return
27 | fi
28 |
29 | case "$words[2]" in
30 | go|.|edit|run)
31 | _all_desks
32 | _wanted desks expl 'desks' compadd -a desks ;;
33 | esac
34 |
--------------------------------------------------------------------------------
/shell_plugins/zsh/desk.plugin.zsh:
--------------------------------------------------------------------------------
1 | # Add our plugin's bin diretory to user's path
2 | PLUGIN_D="$(dirname $0)"
3 | export PATH=${PATH}:${PLUGIN_D}
4 |
--------------------------------------------------------------------------------
/test/bashrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesob/desk/252e6834b4a4a535fe905c6087e7eecfda70e040/test/bashrc
--------------------------------------------------------------------------------
/test/run_tests.fish:
--------------------------------------------------------------------------------
1 | #!/usr/bin/fish
2 | printf "\
3 |
4 |
5 | " | desk init
6 |
7 | cp examples/* ~/.desk/desks/
8 |
9 | set LIST (desk ls)
10 | echo $LIST | grep "hello" >/dev/null
11 | test $status -ne 0; and echo "hello desk not found"; and exit 1
12 |
13 | set CURRENT (env DESK_ENV=$HOME/.desk/desks/hello.fish desk)
14 | echo $CURRENT | grep "say_hello Args: . Say hello to someone." >/dev/null
15 | test $status -ne 0; and echo "say_hello command not found"; and exit 1
16 |
17 | set RAN (desk run hello 'say_hello mrfish')
18 | echo $RAN | grep "Hello mrfish" >/dev/null
19 | test $status -ne 0; and echo "Desk run with 'hello' failed"; and exit 1
20 |
21 | exit 0
22 |
--------------------------------------------------------------------------------
/test/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Run automated tests to ensure desk is behaving as expected
4 |
5 | ensure() {
6 | if [ $1 -ne 0 ]; then
7 | echo "Failed: $2"
8 | exit 1
9 | fi
10 | }
11 |
12 | ensure_not(){
13 | if [ $1 -eq 0 ]; then
14 | echo "Failed: $2"
15 | exit 1
16 | fi
17 | }
18 |
19 | desk | grep "No desk activated." >/dev/null
20 | ensure $? "Desk without desk activated fails."
21 | desk --version | grep "◲ desk " >/dev/null
22 | ensure $? "Desk version fails."
23 |
24 | HELP=$(desk help)
25 | ensure $? "Desk help fails."
26 | echo "$HELP" | grep 'desk init' >/dev/null
27 | ensure $? "Desk help doesn't contain init"
28 | echo "$HELP" | grep 'desk (list|ls)' >/dev/null
29 | ensure $? "Desk help doesn't contain list"
30 | echo "$HELP" | grep 'desk (.|go)' >/dev/null
31 | ensure $? "Desk help doesn't contain go"
32 | echo "$HELP" | grep 'desk run' >/dev/null
33 | ensure $? "Desk help doesn't contain run"
34 | echo "$HELP" | grep 'desk help' >/dev/null
35 | ensure $? "Desk help doesn't contain help"
36 | echo "$HELP" | grep 'desk version' >/dev/null
37 | ensure $? "Desk help doesn't contain version"
38 |
39 | desk init </dev/null
46 | ensure $? "Desk list missing desk (with DESK_DESKS_DIR)"
47 | echo "$LIST" | grep "python_project desk for working on a Python project" >/dev/null
48 | ensure $? "Desk list missing python_project (with DESK_DESKS_DIR)"
49 | echo "$LIST" | grep "terraform desk for doing work on a terraform-based repository" >/dev/null
50 | ensure $? "Desk list missing terraform (with DESK_DESKS_DIR)"
51 | echo "$LIST" | grep "hello simple desk that says hello" >/dev/null
52 | ensure $? "Desk list missing hello (with DESK_DESKS_DIR)"
53 |
54 | rm -rf "$HOME/.desk/desks"
55 | ln -s "$HOME/examples" "$HOME/.desk/desks"
56 |
57 | ## `desk list`
58 |
59 | # --no-format
60 | LIST=$(desk list --no-format)
61 | echo "$LIST" | grep "desk - the desk I use to work on desk :)" >/dev/null
62 | ensure $? "Desk list missing desk with --no-format option (with symlink)"
63 | echo "$LIST" | grep "python_project - desk for working on a Python project" >/dev/null
64 | ensure $? "Desk list missing python_project with --no-format option (with symlink)"
65 | echo "$LIST" | grep "terraform - desk for doing work on a terraform-based repository" >/dev/null
66 | ensure $? "Desk list missing terraform with --no-format option (with symlink)"
67 | echo "$LIST" | grep "hello - simple desk that says hello" >/dev/null
68 | ensure $? "Desk list missing hello (with symlink)"
69 |
70 | # --only-names
71 | LIST=$(desk list --only-names)
72 | echo "$LIST" | grep "the desk I use to work on desk :)" >/dev/null
73 | ensure_not $? "Desk list --only-names contains 'desk' description (with symlink)"
74 | echo "$LIST" | grep -e '^desk$' >/dev/null
75 | ensure $? "Desk list --only-names missing 'desk' (with symlink)"
76 |
77 | # without options
78 | LIST=$(desk list)
79 | echo "$LIST" | grep "desk the desk I use to work on desk :)" >/dev/null
80 | ensure $? "Desk list did not align 'desk' (with symlink)"
81 | echo "$LIST" | grep "python_project desk for working on a Python project" >/dev/null
82 | ensure $? "Desk list did not align 'python_project' (with symlink)"
83 | echo "$LIST" | grep "terraform desk for doing work on a terraform-based repository" >/dev/null
84 | ensure $? "Desk list did not align 'terraform' (with symlink)"
85 |
86 | # DESK_DESKS_DIR=...
87 | rm -rf "$HOME/.desk/desks"
88 | LIST=$(DESK_DESKS_DIR=$HOME/examples desk list)
89 | echo "$LIST" | grep "desk the desk I use to work on desk :)" >/dev/null
90 | ensure $? "Desk list missing desk (with DESK_DESKS_DIR)"
91 | echo "$LIST" | grep "python_project desk for working on a Python project" >/dev/null
92 | ensure $? "Desk list missing python_project (with DESK_DESKS_DIR)"
93 | echo "$LIST" | grep "terraform desk for doing work on a terraform-based repository" >/dev/null
94 | ensure $? "Desk list missing terraform (with DESK_DESKS_DIR)"
95 |
96 | ln -s "$HOME/examples" "$HOME/.desk/desks"
97 |
98 | mkdir ~/terraform-repo
99 |
100 | ## `desk`
101 |
102 | # without options
103 | CURRENT=$(DESK_ENV=$HOME/.desk/desks/terraform.sh desk)
104 | echo "$CURRENT" | grep 'set_aws_env Set up AWS env variables: ' >/dev/null
105 | ensure $? "Desk current terraform missing set_aws_env"
106 | echo "$CURRENT" | grep 'plan Run `terraform plan` with proper AWS var config' >/dev/null
107 | ensure $? "Desk current terraform missing plan"
108 | echo "$CURRENT" | grep 'apply Run `terraform apply` with proper AWS var config' >/dev/null
109 | ensure $? "Desk current terraform missing apply"
110 | echo "$CURRENT" | grep 'config Set up terraform config: ' >/dev/null
111 | ensure $? "Desk current terraform missing config"
112 |
113 | # --no-format
114 | CURRENT=$(DESK_ENV=$HOME/.desk/desks/terraform.sh desk --no-format)
115 | echo "$CURRENT" | grep 'set_aws_env - Set up AWS env variables: ' >/dev/null
116 | ensure $? "Desk current terraform missing set_aws_env"
117 | echo "$CURRENT" | grep 'plan - Run `terraform plan` with proper AWS var config' >/dev/null
118 | ensure $? "Desk current terraform missing plan"
119 | echo "$CURRENT" | grep 'apply - Run `terraform apply` with proper AWS var config' >/dev/null
120 | ensure $? "Desk current terraform missing apply"
121 | echo "$CURRENT" | grep 'config - Set up terraform config: ' >/dev/null
122 | ensure $? "Desk current terraform missing config"
123 |
124 | # testing for exported variables
125 | CURRENT=$(DESK_ENV=$HOME/.desk/desks/hello.sh desk)
126 | echo "$CURRENT" | grep 'MyName Why should I always type my name' >/dev/null
127 | ensure $? "Desk current hello missing exported environment variable"
128 |
129 |
130 | RAN=$(desk run hello 'howdy james!')
131 | echo "$RAN" | grep 'howdy y'"'"'all james!' >/dev/null
132 | ensure $? "Run in desk 'hello' didn't work with howdy alias"
133 |
134 | RAN=$(desk run hello 'hi j')
135 | echo "$RAN" | grep 'hi, j!' >/dev/null
136 | ensure $? "Run in desk 'hello' didn't work with hi function"
137 |
138 | RAN=$(desk run hello 'echo $MyName')
139 | echo "$RAN" | grep 'James' >/dev/null
140 | ensure $? "Run in desk 'hello' didn't work with MyName exported variable"
141 |
142 | RAN=$(desk run hello echo ahoy matey)
143 | echo "$RAN" | grep 'ahoy matey' >/dev/null
144 | ensure $? "Run in desk 'hello' didn't work with argument vector"
145 |
146 | ## `desk go`
147 |
148 | RAN=$(desk go example-project/Deskfile -c 'desk ; exit')
149 | echo "$RAN" | grep "example-project - simple desk that says hello" >/dev/null
150 | ensure $? "Deskfile invocation didn't work (example-project/)"
151 | echo "$RAN" | grep -E "hi\s+" >/dev/null
152 | ensure $? "Deskfile invocation didn't work (example-project/)"
153 | echo "$RAN" | grep -E "howdy\s+" >/dev/null
154 | ensure $? "Deskfile invocation didn't work (example-project/)"
155 |
156 | RAN=$(desk go example-project/ -c 'desk ; exit')
157 | echo "$RAN" | grep "example-project - simple desk that says hello" >/dev/null
158 | ensure $? "Deskfile invocation didn't work (example-project/)"
159 | echo "$RAN" | grep -E "hi\s+" >/dev/null
160 | ensure $? "Deskfile invocation didn't work (example-project/)"
161 | echo "$RAN" | grep -E "howdy\s+" >/dev/null
162 | ensure $? "Deskfile invocation didn't work (example-project/)"
163 |
164 | pushd example-project
165 |
166 | RAN=$(desk go . -c 'desk ; exit')
167 | echo "$RAN" | grep "example-project - simple desk that says hello" >/dev/null
168 | ensure $? "Deskfile invocation didn't work (./)"
169 | echo "$RAN" | grep -E "hi\s+" >/dev/null
170 | ensure $? "Deskfile invocation didn't work (example-project/)"
171 | echo "$RAN" | grep -E "howdy\s+" >/dev/null
172 | ensure $? "Deskfile invocation didn't work (example-project/)"
173 |
174 | popd
175 |
176 | echo "tests pass."
177 |
--------------------------------------------------------------------------------
/test/zshrc:
--------------------------------------------------------------------------------
1 | # Set up the prompt
2 |
3 | autoload -Uz promptinit
4 | promptinit
5 | prompt adam1
6 |
7 | setopt histignorealldups sharehistory
8 |
9 | # Use emacs keybindings even if our EDITOR is set to vi
10 | bindkey -e
11 |
12 | # Keep 1000 lines of history within the shell and save it to ~/.zsh_history:
13 | HISTSIZE=1000
14 | SAVEHIST=1000
15 | HISTFILE=~/.zsh_history
16 |
17 | # Use modern completion system
18 | autoload -Uz compinit
19 | compinit
20 |
21 | zstyle ':completion:*' auto-description 'specify: %d'
22 | zstyle ':completion:*' completer _expand _complete _correct _approximate
23 | zstyle ':completion:*' format 'Completing %d'
24 | zstyle ':completion:*' group-name ''
25 | zstyle ':completion:*' menu select=2
26 | eval "$(dircolors -b)"
27 | zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
28 | zstyle ':completion:*' list-colors ''
29 | zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s
30 | zstyle ':completion:*' matcher-list '' 'm:{a-z}={A-Z}' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=* l:|=*'
31 | zstyle ':completion:*' menu select=long
32 | zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s
33 | zstyle ':completion:*' use-compctl false
34 | zstyle ':completion:*' verbose true
35 |
36 | zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31'
37 | zstyle ':completion:*:kill:*' command 'ps -u $USER -o pid,%cpu,tty,cputime,cmd'
38 |
--------------------------------------------------------------------------------