├── LICENSE
├── README.md
├── my-alternatives-debian
└── my-alternatives-redhat
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 TekWizely
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # My-Alternatives
(hacking update-alternatives to make local changes)
2 | [](https://github.com/tekwizely/pre-commit-golang/blob/master/LICENSE)
3 |
4 | My-Alternatives is a light-weight wrapper over _update-alternatives_, offering user-level customizations.
5 |
6 | Supports _Debian_, _SUSE_*, and _RedHat_
7 |
8 | * for suse support, use the debian version
9 |
10 | -----------------------
11 | ### Easy to Get Started
12 |
13 | With my-alternatives, configuring custom alternatives is as easy as:
14 |
15 | _home configuration example_
16 | ```shell
17 | # initialize my-alternatives
18 | # note: place this in your .profile
19 | $ eval "$( my-alternatives init )"
20 |
21 | # customize an alternative
22 | $ my-alternatives select
23 | ```
24 |
25 | Your selections will be saved into your `HOME` configuration, and made active in any login shell that performs the initialization routine.
26 |
27 | **NOTE:** You can save the initialization routine in your `.profile`
28 |
29 | ### Per-Shell Configuration
30 |
31 | If you want to make a temporary change that only modifies your current shell session, you can initialize a `TEMP` configuration:
32 |
33 | _temp configuration example_
34 | ```shell
35 | # initialize a temporary configuration
36 | # note: your HOME configuration must already be initialized
37 | $ my-alternatives init-tmp
38 |
39 | # customize an alternative for just the current shell
40 | $ my-alternatives select
41 | ```
42 |
43 | ### Auto-Import
44 |
45 | The first time you `select` an alternative, my-alternatives automatically imports the alternative group into your configuration.
46 |
47 | When the `HOME` configuration is the active configuration, my-alternatives imports the group from the system-level configuration.
48 |
49 | When a `TEMP` configuration is active, my-alternatives first tries to import from your `HOME` configuration, falling back onto the system-level configuration if the group is not present.
50 |
51 | ### Manual Import
52 |
53 | If you want to import an alternative group into your active configuration without selecting an alternative, you can use the `import` command:
54 |
55 | _import example_
56 | ```shell
57 | $ my-alternatives import
58 | ```
59 | -----------
60 | ## Commands
61 |
62 | Below are tables of the available commands for each supported OS
63 |
64 | ### Debian / SUSE
65 |
66 | **NOTE:** _SUSE_ uses a [rebranded](https://build.opensuse.org/package/view_file/openSUSE:Leap:15.2/update-alternatives/update-alternatives-suse.patch?expand=1) version of _Debian's_ udpate-alternatives.
67 |
68 | Until such time as the feature set of the two versions diverges, this project will **just** maintain the _Debian_ vesion.
69 |
70 | #### My-Alternatives-Debian Commands
71 |
72 | Below is the list of custom commands that _my-alternatives-debian_ implements:
73 |
74 | | Command | Description
75 | |--------------------|------------
76 | | `init`, `shellenv` | Prepare the current shell session for user-level alternatives.
77 | | `init-tmp`, `tmp` | Configure the current shell session for temporary (short-lived) changes.
78 | | `rm-tmp` | Remove the temporary configuration from the current shell session, making the `HOME` configuration active.
79 | | `select`, `config` | Select the active alternative for a group. This is equivalent to `update-alternatives --config` with the adition of the auto-import logic.
80 | | `import` | Import an alternative group into the current configuration.
81 | | `add` | Add an alternative to a group within the current configuration. This is equivalent to `update-alternatives --install` but has _slightly_ different syntax. see `my-alternatives help add` for details.
82 | | `version` | Display my-alternatives version number.
83 |
84 | **NOTE:** See `my-alternatives help ` to learn about a specific command, including additional options.
85 |
86 | #### Debian Update-Alternatives Commands
87 |
88 | Below is the list of commands that are implemented as pass-through to the related _Debian_ update-alternatives command:
89 |
90 | | My-Alternatives Command | Update-Alternatives Command
91 | |-------------------------|----------------------------
92 | | `select`, `config` | `--config`
93 | | `select-all`, `--config-all` | `--all`
94 | | `rm-alt` | `--remove`
95 | | `rm-group`, `rm-grp` | `--remove-all`
96 | | `query` | `--query`
97 | | `display` | `--display`
98 | | `list` | `--list`
99 | | `set` | `--set`
100 | | `auto` | `--auto`
101 | | `get-selections` | `--get-selections`
102 | | `set-selections` | `--set-selections`
103 | | `ua-help` | `--help`
104 | | `ua-version` | `--version`
105 |
106 | My-Alternatives will set the `--admindir` and `--altdir` options to point to your active configuration.
107 |
108 | All additional command-line options are passed-through, unmodified.
109 |
110 | **NOTE:** See `update-alternatives --help` or `man update-alternatives` to learn more about the various commands and their options.
111 |
112 | ### RedHat
113 |
114 | #### hacking The system
115 |
116 | _RedHat_ has its own implementation of update-alternatives, which is slightly different from the _Debian_ version.
117 |
118 | One **major** difference is that it does NOT support the `--query` option, meaning that there's no means of determining an anternative's full configuraiton using _just_ the tool's public API.
119 |
120 | In order to support this version, we have to use knowledge of the system's _private_ API. Namely:
121 |
122 | * Defaulting the "admin" directory to `/var/lib/alternatives`
123 | * Assuming the name and format of files in the admin directory
124 | * Defaulting the "alt" directory to `/etc/alternatives`
125 | * Assuming the name and nature of files in the alt directory
126 |
127 | #### My-Alternatives-RedHat Commands
128 |
129 | Below is the list of custom commands that _my-alternatives-redhat_ implements:
130 |
131 | | Command | Description
132 | |--------------------|------------
133 | | `init`, `shellenv` | Prepare the current shell session for user-level alternatives.
134 | | `init-tmp`, `tmp` | Configure the current shell session for temporary (short-lived) changes.
135 | | `rm-tmp` | Remove the temporary configuration from the current shell session, making the `HOME` configuration active.
136 | | `select`, `config` | Select the active alternative for a group. This is equivalent to `update-alternatives --config` with the adition of the auto-import logic.
137 | | `import` | Import an alternative group into the current configuration.
138 | | `add` | Add an alternative to a group within the current configuration. This is equivalent to `update-alternatives --install` but has _slightly_ different syntax. see `my-alternatives help add` for details.
139 | | `add-child` | Add an child to an existing alternative for a group within the current configuration. This is equivalent to `update-alternatives --add-slave` but has _slightly_ different syntax. see `my-alternatives help add-child` for details.
140 | | `version` | Display my-alternatives version number.
141 |
142 | **NOTE:** See `my-alternatives help ` to learn about a specific command, including additional options.
143 |
144 | #### RedHat Update-Alternatives Commands
145 |
146 | Below is the list of commands that are implemented as pass-through to the related _RedHat_ update-alternatives command:
147 |
148 | | My-Alternatives Command | Update-Alternatives Command
149 | |-------------------------|----------------------------
150 | | `select`, `config` | `--config`
151 | | `add-child` | `--add-slave`
152 | | `rm-alt` | `--remove`
153 | | `rm-group`, `rm-grp` | `--remove-all`
154 | | `rm-child` | `--remove-slave`
155 | | `display` | `--display`
156 | | `list` | `--list`
157 | | `set` | `--set`
158 | | `auto` | `--auto`
159 | | `ua-help` | `--help`
160 | | `ua-version` | `--version`
161 |
162 | My-Alternatives will set the `--admindir` and `--altdir` options to point to your active configuration.
163 |
164 | All additional command-line options are passed-through, unmodified.
165 |
166 | **NOTE:** See `update-alternatives --help` or `man update-alternatives` to learn more about the various commands and their options.
167 |
168 | ### Invoking Update-Alternatives Directly
169 |
170 | If you need to pass a specific command to update-alternatives, you can do so using the `ua` command:
171 |
172 | _invoke update-alternatives directly_
173 | ```shell
174 | $ my-alternatives ua --display pager
175 | ```
176 |
177 | My-Alternatives will set the `--admindir` and `--altdir` options to point to your active configuration.
178 |
179 | All additional command-line options are passed-through, unmodified.
180 |
181 | **NOTE:** See `update-alternatives --help` or `man update-alternatives` to learn more about available commands and their options.
182 |
183 | -------------
184 | ## Installing
185 |
186 | ### Releases
187 |
188 | See the [Releases](https://github.com/TekWizely/my-alternatives/releases) page for downloadable archives of versioned releases.
189 |
190 | ### Git
191 |
192 | ```
193 | git clone git://github.com/TekWizely/my-alternatives.git
194 | ```
195 |
196 | ### Renaming the Scripts
197 |
198 | Depending on how you acquire the files, the scripts may be named by their OS flavor, i.e:
199 | * Debian : `my-alternatives-debian`
200 | * RedHat : `my-alternatives-redhat`
201 |
202 | Feel free to rename them as desired. Personally, I rename the script **AND** set up a few convenient aliases:
203 | ```shell
204 | $ cp my-alternatives-debian ~/bin/my-alternatives
205 | $ alias ma="my-alternatives"
206 | $ alias mua="my-alternatives ua"
207 | ```
208 |
209 | ---------------
210 | ## Contributing
211 |
212 | To contribute to My-Alternatives, follow these steps:
213 |
214 | 1. Fork this repository.
215 | 2. Create a branch: `git checkout -b `.
216 | 3. Make your changes and commit them: `git commit -m ''`
217 | 4. Push to the original branch: `git push origin /`
218 | 5. Create the pull request.
219 |
220 | Alternatively see the GitHub documentation on [creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request).
221 |
222 | ----------
223 | ## Contact
224 |
225 | If you want to contact me you can reach me at TekWizely@gmail.com.
226 |
227 | ----------
228 | ## License
229 |
230 | The `tekwizely/my-alternatives` project is released under the [MIT](https://opensource.org/licenses/MIT) License. See `LICENSE` file.
231 |
232 | ----------------
233 | ##### Misc Notes
234 |
235 | As of `v0.7.0`, my-alternatives has been split (and renamed) into two scripts: `my-alternatives-debian` and `my-alternatives-redhat`.
236 |
237 | As of `v0.6.0`, my-alternatives is a complete re-write. As much as possible, commands are implemented as pass-through to _update-alternatives_, pointing to your active configuration.
238 |
239 | My-Alternatives does not require _root / sudo_ privileges to use, as it creates and maintains user-owned configuration directories.
240 |
241 | **CAVEATS:**
242 | - Written in _Bash_
243 | - Requires the following system tools:
244 | - `update-alternatives`
245 | - `readlink` (redhat version)
246 | - `mktemp` (debian version)
247 | - `umask`
248 | - `dirname`
249 | - `basename`
250 | - `manpath` _(optional)_
251 | - Utilizes tmp files / directories
252 | - Sets `umask 077` for safety
253 | - Tested on:
254 | - Ubuntu
255 | - `Ubuntu 20.04.3 LTS`
256 | - `Debian update-alternatives version 1.19.7.`
257 | - `Bash 5.0.17(1)-release`
258 | - openSUSE
259 | - `openSUSE Leap 15.3`
260 | - `SUSE update-alternatives version 1.19.0.4.`
261 | - `Bash 4.4.23(1)-release`
262 | - CentOS
263 | - `CentOS Linux release 8.4.2105`
264 | - `alternatives version 1.13`
265 | - `GNU bash, version 4.4.19(1)-release`
266 |
--------------------------------------------------------------------------------
/my-alternatives-debian:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #######################################################################
3 | # SPDX-License-Identifier: MIT
4 | # Copyright (c) 2021 TekWizely & co-authors
5 | #
6 | # Use of this source code is governed by the MIT license.
7 | # See the accompanying LICENSE file, if present, or visit:
8 | # https://opensource.org/licenses/MIT
9 | #######################################################################
10 | VERSION="v0.7.0-debian"
11 |
12 | set -Eeuo pipefail
13 | umask 077 # We use tmp files. Try to protect the user
14 |
15 | function usage() {
16 | # Need to escape $ and \
17 | cat <<-USAGE
18 | my-alternatives (${VERSION})
19 |
20 | a wrapper for update-alternatives to enable user-level configuration.
21 | also supports temporary (per-shell) changes.
22 |
23 | usage:
24 |
25 | eval "\$( my-alternatives [--shell ] [alt_home] )"
26 | prepare the current shell session for user-level alternatives
27 | NOTE: place this in your .profile
28 | my-alternatives [tmp_dir]
29 | configure the current shell session for temporary (short-lived) changes
30 | my-alternatives
31 | remove the temporary configuration from the current shell session
32 | my-alternatives [option ...]
33 | invoke a command
34 | my-alternatives help
35 | learn more about a specific command
36 |
37 | custom commands:
38 |
39 | import
40 | import the alternative group into the current configuration
41 | when the default configuration is active, imports from system-level alternatives
42 | when a tmporary configuration is active, first tries the default configuration, then system-level alternatives
43 | add ...
44 | add an alternative to the group within the current configuration
45 | see 'help add' for usage details
46 | select | config
47 | select the active alternative for group
48 | will first invoke 'import ' if the group is not already available in the current configuration
49 | version
50 | display my-alternatives version number
51 |
52 | renamed pass-through commands:
53 |
54 | select-all | config-all
55 | invoke 'update-alternatives --all'
56 | rm-alt | rmalt
57 | invoke 'update-alternatives --remove'
58 | rm-group | rmgrp
59 | invoke 'update-alternatives --remove-all'
60 |
61 | these have been renamed to reduce ambiguity
62 |
63 | same-name pass-through commands:
64 |
65 | query | display | list | set | auto | get-selections | set-selections
66 |
67 | invoke 'update-alternatives --'
68 |
69 | update-alternatives helper commands:
70 |
71 | ua [option ...]
72 | invoke 'update-alternatives [option ...]' pointing to the currently-configured alternatives
73 | ua-help
74 | invoke 'update-alternatives --help'
75 | ua-version
76 | invoke 'update-alternatives --version'
77 |
78 | USAGE
79 | exit 2
80 | }
81 |
82 | function version() {
83 | printf "%s\n" "${VERSION}"
84 | }
85 |
86 | function main() {
87 |
88 | case "${1:-}" in
89 | -h | --help)
90 | usage
91 | ;;
92 | help)
93 | shift
94 | case "${1:-}" in
95 | init)
96 | help_init
97 | ;;
98 | shellenv) # alias
99 | help_alias "init" "$1"
100 | ;;
101 | init-tmp)
102 | help_init_tmp
103 | ;;
104 | init-temp | tmp | temp) # aliases
105 | help_alias "init-tmp" "$1"
106 | ;;
107 | rm-tmp)
108 | help_rm_tmp
109 | ;;
110 | rm-temp | rmtmp | rmtemp | remove-tmp | remove-temp | removetmp | removetemp) # aliases
111 | help_alias "rm-tmp" "$1"
112 | ;;
113 | import)
114 | help_import
115 | ;;
116 | add)
117 | help_add
118 | ;;
119 | select)
120 | help_select
121 | ;;
122 | config) # alias
123 | help_alias "select" "$1"
124 | ;;
125 | version)
126 | version
127 | ;;
128 | #
129 | # renamed update-alternatives pass-through
130 | #
131 | select-all | config-all)
132 | help_passthrough "all" "$1"
133 | ;;
134 | rm-alt | rmalt | remove-alt | removealt)
135 | help_passthrough "remove" "$1"
136 | ;;
137 | rm-group | rmgroup | rm-grp | rmgrp | remove-group | removegroup | remove-grp | removegrp)
138 | help_passthrough "remove-all" "$1"
139 | ;;
140 | #
141 | # update-alternatives pass-through
142 | #
143 | query | display | list | set | auto | get-selections | set-selections)
144 | help_passthrough "$1"
145 | ;;
146 | #
147 | # update-alternatives helpers
148 | #
149 | ua)
150 | help_update_alternatives
151 | ;;
152 | ua-help)
153 | help_update_alternatives_help
154 | ;;
155 | ua-version)
156 | help_update_alternatives_version
157 | ;;
158 | *)
159 | if [ -z "${1:-}" ]; then
160 | printf "usage: help \n" >&2
161 | else
162 | printf "error: unrecognized command: %s\n" "${1}" >&2
163 | fi
164 | printf "\nsee 'my-alternatives --help' for list of commands\n" >&2
165 | exit 2
166 | ;;
167 | esac
168 | ;;
169 | init | shellenv)
170 | shift
171 | do_init "$@"
172 | ;;
173 | init-tmp | init-temp | tmp | temp)
174 | shift
175 | do_init_tmp "$@"
176 | ;;
177 | rm-tmp | rm-temp | rmtmp | rmtemp | remove-tmp | remove-temp | removetmp | removetemp)
178 | shift
179 | do_rm_tmp "$@"
180 | ;;
181 | import)
182 | shift
183 | do_import "$@"
184 | ;;
185 | add)
186 | shift
187 | do_add "$@"
188 | ;;
189 | select | config)
190 | shift
191 | do_select "$@"
192 | ;;
193 | version)
194 | version
195 | ;;
196 | #
197 | # renamed update-alternatives pass-through
198 | #
199 | select-all | config-all)
200 | shift
201 | do_update_alternatives "--all" "$@"
202 | ;;
203 | rm-alt | rmalt | remove-alt | removealt)
204 | shift
205 | do_update_alternatives "--remove" "$@"
206 | ;;
207 | rm-group | rmgroup | rm-grp | rmgrp | remove-group | removegroup | remove-grp | removegrp)
208 | shift
209 | do_update_alternatives "--remove-all" "$@"
210 | ;;
211 | #
212 | # update-alternatives pass-through
213 | #
214 | query | display | list | set | auto | get-selections | set-selections)
215 | local cmd="${1}"
216 | shift
217 | do_update_alternatives "--${cmd}" "$@"
218 | ;;
219 | #
220 | # update-alternatives helpers
221 | #
222 | ua)
223 | shift
224 | do_update_alternatives "$@"
225 | ;;
226 | ua-help)
227 | shift
228 | command update-alternatives --help "$@"
229 | ;;
230 | ua-version)
231 | shift
232 | command update-alternatives --version "$@"
233 | ;;
234 | *)
235 | if [ -z "${1:-}" ]; then
236 | printf "usage: my-alternatives [option ...]\n" >&2
237 | else
238 | printf "error: unrecognized command: %s\n" "${1}" >&2
239 | fi
240 | printf "\nsee 'my-alternatives --help' for list of commands\n" >&2
241 | exit 2
242 | ;;
243 | esac
244 | }
245 |
246 | function help_init() {
247 | cat <<-'USAGE'
248 | usage: eval "$( my-alternatives [--shell ] [alt_home] )"
249 |
250 | prepares the current shell session for user-level alternatives.
251 |
252 | if [alt_home] is provided, it will be configured as MY_ALTS_HOME
253 |
254 | when [alt_home] is NOT provided, the following logic will be used determine MY_ALTS_HOME:
255 |
256 | * check if $MY_ALTS_HOME already defined
257 | * check $XDG_CONFIG_HOME. if present, use $XDG_CONFIG_HOME/my-alternatives
258 | * finally default to ~/.config/my-alternatives
259 |
260 | when [--shell ] is NOT provided, checks $SHELL.
261 |
262 | NOTE: Currently only 'bash' is supported.
263 |
264 | creates the MY_ALTS_HOME directory and required sub-directories if they don't already exist.
265 |
266 | finally, configures PATH and MANPATH.
267 |
268 | example:
269 |
270 | # prepare shell with default MY_ALTS_HOME logic
271 | # NOTE: you can place this in your .profile
272 | $ eval "$( command my-alternatives init )"
273 |
274 | # = ~/.config/my-alternatives
275 | export MY_ALTS_HOME=""
276 | export PATH="/bin${PATH:+:$PATH}"
277 | export MANPATH="/man:${MANPATH:-}"
278 |
279 | more info:
280 |
281 | see 'my-alternatives --help' to learn more about configuring your own alternatives.
282 |
283 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives.
284 |
285 | USAGE
286 | exit 2
287 | }
288 |
289 | function do_init() {
290 | local alt_home shell
291 | while (($#)); do
292 | case "${1}" in
293 | --shell)
294 | if [ -z "${2:-}" ]; then
295 | printf "my-alternatives: error: no value provided for --shell option. see 'my-alternatives help init' for usage\n" >&2
296 | exit 2
297 | fi
298 | # basename as a courtesy in case they use $SHELL/etc
299 | #
300 | shell="$(basename "${2}")"
301 | shift 2
302 | ;;
303 | *)
304 | # If alt_home already defined
305 | #
306 | if [ -n "${alt_home:-}" ]; then
307 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help init' for usage\n" "${1}" >&2
308 | exit 2
309 | fi
310 | alt_home="${1}"
311 | shift
312 | ;;
313 | esac
314 | done
315 | # If --shell not provided, check $SHELL
316 | #
317 | if [ -z "${shell:-}" ]; then
318 | shell="$(basename "${SHELL:-}")"
319 | if [ -z "${shell}" ]; then
320 | printf "my-alternatives: error: Unable to determine shell. see 'my-alternatives help init' for usage\n" >&2
321 | exit 2
322 | fi
323 | fi
324 | # Confirm shell supported
325 | #
326 | if [[ "${shell}" != "bash" ]]; then
327 | printf "my-alternatives: error: unrecognized shell: %s. see 'my-alternatives help init' for usage\n" "${shell}" >&2
328 | exit 2
329 | fi
330 | #
331 | # set from specified values or default logic
332 | #
333 | MY_ALTS_HOME="${alt_home:-${MY_ALTS_HOME:-${XDG_CONFIG_HOME:-${HOME:?}/.config}/my-alternatives}}"
334 | # print assignments seperately as I don't want to escape ALL the other $ :)
335 | #
336 | printf '_MY_ALTS_HOME="%s"\n' "${MY_ALTS_HOME:?}"
337 | # path clean up adapted from: https://stackoverflow.com/a/2108540/1822537
338 | #
339 | cat <<-'INIT'
340 | if [ -n "${_MY_ALTS_HOME}:-" ] && command mkdir -p -- "${_MY_ALTS_HOME}"; then
341 | command mkdir -p -- "${_MY_ALTS_HOME}/bin"
342 | command mkdir -p -- "${_MY_ALTS_HOME}/man"
343 | command mkdir -p -- "${_MY_ALTS_HOME}/man/man"{1..9}
344 | command mkdir -p -- "${_MY_ALTS_HOME}/admin"
345 | command mkdir -p -- "${_MY_ALTS_HOME}/alts"
346 | # clean up PATH and add to front
347 | PATH=":${PATH:-}:"
348 | PATH="${PATH/":${_MY_ALTS_HOME}/bin:"/:}"
349 | PATH="${PATH%:}"
350 | PATH="${PATH#:}"
351 | PATH="${_MY_ALTS_HOME}/bin${PATH:+:$PATH}"
352 | # clean up MANPATH and add to front
353 | MANPATH=":${MANPATH:-}:"
354 | MANPATH="${MANPATH/":${_MY_ALTS_HOME}/man:"/:}"
355 | MANPATH="${MANPATH%:}"
356 | MANPATH="${MANPATH#:}"
357 | MANPATH="${_MY_ALTS_HOME}/man:${MANPATH:-}"
358 | # add helper function
359 | my-alternatives() {
360 | case "${1:-}" in
361 | # some commands require eval
362 | init | shellenv | \
363 | init-tmp | init-temp | tmp | temp | \
364 | rm-tmp | rm-temp | rmtmp | rmtemp | remove-tmp | remove-temp | removetmp | removetemp)
365 | eval "$( command my-alternatives "$@" )"
366 | ;;
367 | *)
368 | command my-alternatives "$@"
369 | ;;
370 | esac
371 | }
372 | # finally, export variables
373 | command export MY_ALTS_HOME="${_MY_ALTS_HOME}"
374 | fi
375 | command unset _MY_ALTS_HOME
376 | INIT
377 | }
378 |
379 | function help_init_tmp() {
380 | cat <<-'USAGE'
381 | usage: my-alternatives [tmp_dir]
382 |
383 | configures the current shell session for temporary (short-lived) changes.
384 |
385 | if [tmp_dir] is provided, it will be configured as MY_ALTS_TMP
386 |
387 | when [tmp_dir] is NOT provided, the following logic will be used determine MY_ALTS_TMP:
388 |
389 | * check if $MY_ALTS_TMP already defined
390 | * check XDG_CACHE_HOME. if present, use XDG_CACHE_HOME/my-alternatives/
391 | * finally default to ~/.config/my-alternatives/
392 |
393 | creates the MY_ALTS_TMP directory and required sub-directories if they don't already exist.
394 |
395 | finally, configures PATH and MANPATH.
396 |
397 | example:
398 |
399 | $ my-alternatives init-tmp
400 |
401 | # = ~/.config/my-alternatives/abcd123
402 | export MY_ALTS_TMP=""
403 | export PATH="/bin${PATH:+:$PATH}"
404 | export MANPATH="/man:${MANPATH:-}"
405 |
406 | more info:
407 |
408 | see 'my-alternatives --help' to learn more about configuring your own alternatives.
409 |
410 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives.
411 |
412 | USAGE
413 | exit 2
414 | }
415 |
416 | function do_init_tmp() {
417 | # Ensure alt_home configured
418 | #
419 | if [ -z "${MY_ALTS_HOME:-}" ]; then
420 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2
421 | exit 2
422 | fi
423 | local alt_tmp
424 | while (($#)); do
425 | case "${1}" in
426 | *)
427 | # If alt_root already defined
428 | #
429 | if [ -n "${alt_tmp:-}" ]; then
430 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help init-tmp' for usage\n" "${1}" >&2
431 | exit 2
432 | fi
433 | alt_tmp="${1}"
434 | shift
435 | ;;
436 | esac
437 | done
438 | # create temp folder if no alt_tmp or MY_ALTS_TMP given
439 | #
440 | MY_ALTS_TMP="${alt_tmp:-${MY_ALTS_TMP:-}}"
441 | if [ -z "${MY_ALTS_TMP}" ]; then
442 | while :; do
443 | MY_ALTS_TMP="${XDG_CACHE_HOME:-"${HOME:?}/.cache"}/my-alternatives/${RANDOM}${RANDOM}"
444 | [ -d "${MY_ALTS_TMP}" ] || break
445 | done
446 | fi
447 | # print assignment seperately as I don't want to escape ALL the other $ :)
448 | #
449 | printf '_MY_ALTS_TMP="%s"\n' "${MY_ALTS_TMP:?}"
450 | cat <<-'TMP'
451 | if [ -n "${_MY_ALTS_TMP}:-" ] && command mkdir -p -- "${_MY_ALTS_TMP}"; then
452 | command mkdir -p -- "${_MY_ALTS_TMP}/bin"
453 | command mkdir -p -- "${_MY_ALTS_TMP}/man"
454 | command mkdir -p -- "${_MY_ALTS_TMP}/man/man"{1..9}
455 | command mkdir -p -- "${_MY_ALTS_TMP}/admin"
456 | command mkdir -p -- "${_MY_ALTS_TMP}/alts"
457 | # clean up PATH and add to front
458 | PATH=":${PATH:-}:"
459 | PATH="${PATH/":${_MY_ALTS_TMP}/bin:"/:}"
460 | PATH="${PATH%:}"
461 | PATH="${PATH#:}"
462 | PATH="${_MY_ALTS_TMP}/bin${PATH:+:$PATH}"
463 | # clean up MANPATH and add to front
464 | MANPATH=":${MANPATH:-}:"
465 | MANPATH="${MANPATH/":${_MY_ALTS_TMP}/man:"/:}"
466 | MANPATH="${MANPATH%:}"
467 | MANPATH="${MANPATH#:}"
468 | MANPATH="${_MY_ALTS_TMP}/man:${MANPATH:-}"
469 | # finally, export tmp variable
470 | command export MY_ALTS_TMP="${_MY_ALTS_TMP}"
471 | fi
472 | command unset _MY_ALTS_TMP
473 | TMP
474 | }
475 |
476 | function help_rm_tmp() {
477 | cat <<-'USAGE'
478 | usage: my-alternatives rm-tmp
479 |
480 | removes the temporary configuration from the current shell session.
481 |
482 | restores the curent session to the default user-level alternatives.
483 |
484 | removes the temporary directory referenced by MY_ALTS_TMP
485 |
486 | removes MY_ALTS_TMP from both PATH and MANPATH
487 |
488 | finall, unsets the MY_ALTS_TMP variable
489 |
490 | more info:
491 |
492 | see 'my-alternatives --help' to learn more about creating a temporary configuration.
493 |
494 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives.
495 |
496 | USAGE
497 | exit 2
498 | }
499 |
500 | function do_rm_tmp() {
501 | # Ensure alt_home configured
502 | #
503 | if [ -z "${MY_ALTS_HOME:-}" ]; then
504 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2
505 | exit 2
506 | fi
507 | # No further arguments expected
508 | #
509 | if [[ $# -gt 0 ]]; then
510 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help rm-tmp' for usage\n" "${1}" >&2
511 | exit 2
512 | fi
513 | cat <<-'RMTMP'
514 | if [ -n "${MY_ALTS_TMP:-}" ]; then
515 | # clean up temp dir
516 | if [ -d "${MY_ALTS_TMP}" ]; then
517 | command rm -rf "${MY_ALTS_TMP}/"
518 | fi
519 | # clean up PATH
520 | PATH=":${PATH:-}:"
521 | PATH="${PATH/":${MY_ALTS_TMP}/bin:"/:}"
522 | PATH="${PATH%:}"
523 | PATH="${PATH#:}"
524 | # clean up MANPATH
525 | MANPATH=":${MANPATH:-}:"
526 | MANPATH="${MANPATH/":${MY_ALTS_TMP}/man:"/:}"
527 | MANPATH="${MANPATH%:}"
528 | MANPATH="${MANPATH#:}"
529 | fi
530 | # finally, clean up tmp variable
531 | command unset MY_ALTS_TMP
532 | RMTMP
533 | }
534 |
535 | function help_import() {
536 | cat <<-'USAGE'
537 | usage: my-alternatives import
538 |
539 | imports an alternative group into the currently-configured alternatives.
540 |
541 | when the default alternatives (ie. MY_ALTS_HOME) are active:
542 | * tries to import from the system-level alternatives
543 |
544 | when temporary alternatives (i.e MY_ALTS_TMP) are active:
545 | * first tries to import from the default alternatives
546 | * finally tries to import from the system-level alternatives
547 |
548 | more info:
549 |
550 | see 'my-alternatives --help' to learn more about configuring your own alternatives.
551 |
552 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives.
553 |
554 | USAGE
555 | exit 2
556 | }
557 |
558 | function do_import() {
559 | # Ensure alt_home configured
560 | #
561 | if [ -z "${MY_ALTS_HOME:-}" ]; then
562 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2
563 | exit 2
564 | fi
565 | local alt_root="${MY_ALTS_TMP:-${MY_ALTS_HOME}}"
566 | local name
567 | while (($#)); do
568 | case "${1}" in
569 | *)
570 | # If name already defined
571 | #
572 | if [ -n "${name:-}" ]; then
573 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help import' for usage\n" "${1}" >&2
574 | exit 2
575 | fi
576 | name="${1}"
577 | shift
578 | ;;
579 | esac
580 | done
581 | # No name argument
582 | #
583 | if [[ -z "${name:-}" ]]; then
584 | printf "my-alternatives: error: no name provided. see 'my-alternatives help import' for usage\n" >&2
585 | exit 1
586 | fi
587 | local from # for status message
588 | local data_file
589 | # Create temp file
590 | #
591 | if ! data_file="$(command mktemp -q -t "my-alts-${name}-query.XXXXXXXX")"; then
592 | printf "my-alternatives: error: unable to create temp file\n" >&2
593 | exit 2
594 | fi
595 | # sanity check
596 | if [ ! -f "${data_file}" ]; then
597 | printf "my-alternatives: error: unable to create temp file\n" >&2
598 | exit 2
599 | fi
600 | # If tmp is active try import from home
601 | #
602 | if [ "${alt_root}" = "${MY_ALTS_TMP:-}" ]; then
603 | # does NOT exit on error - checks data_file for success
604 | command update-alternatives \
605 | --altdir "${MY_ALTS_HOME}/alts" --admindir "${MY_ALTS_HOME}/admin" \
606 | --query "${name}" \
607 | >"${data_file}" 2>/dev/null || true
608 |
609 | if [ -s "${data_file}" ]; then
610 | from="MY_ALTS_HOME: ${MY_ALTS_HOME}" # for status message
611 | fi
612 | fi
613 | if [ -z "${from:-}" ]; then
614 | # final fallback = update-alternatives default
615 | # exits on error
616 | command update-alternatives --query "${name}" >"${data_file}"
617 |
618 | if [ -s "${data_file}" ]; then
619 | from="system-level alternative" # for status message
620 | fi
621 | fi
622 | # final sanity check
623 | #
624 | if [ -z "${from:-}" ]; then
625 | printf "my-alternatives: error: unable to locate alternative group with name %s\n" "${name}" >&2
626 | rm "${data_file}"
627 | exit 2
628 | fi
629 |
630 | alt_parse_query "${data_file}" || return $?
631 | rm "${data_file}"
632 |
633 | if [ -z "${ALT_GROUP_LINK}" ]; then
634 | printf "my-alternatives: error: unable to determine provided location\n" >&2
635 | exit 2
636 | fi
637 |
638 | printf "importing alternative group %s%s from %s\n" "${ALT_GROUP_NAME}" "${ALT_GROUP_STATUS:+" in ${ALT_GROUP_STATUS} mode"}" "${from}"
639 |
640 | local alt_group_link_rel
641 | alt_group_link_rel="$(get_my_alt_rel_path "${ALT_GROUP_LINK}")"
642 | if [ "${alt_group_link_rel}" == "${ALT_GROUP_LINK}" ]; then
643 | printf "my-alternatives: error: unable to determine relative path for primary link: %s\n" "${ALT_GROUP_LINK}" >&2
644 | exit 2
645 | fi
646 | ALT_GROUP_LINK="${alt_root}${alt_group_link_rel}"
647 | local a=0
648 | while [[ $a -lt ${#ALT_GROUP_VALUES[@]} ]]; do
649 | local alt_value="${ALT_GROUP_VALUES[$a]}"
650 | local alt_priority="${ALT_GROUP_PRIORITIES[$a]}"
651 | local child_args=()
652 | local s=0
653 | while [[ $s -lt ${#ALT_GROUP_SLAVE_NAMES[@]} ]]; do
654 | local child_name="${ALT_GROUP_SLAVE_NAMES[$s]}"
655 | local child_link="${ALT_GROUP_SLAVE_LINKS[$s]}"
656 | local child_link_rel
657 | child_link_rel="$(get_my_alt_rel_path "${child_link}")"
658 | if [ "${child_link_rel}" == "${child_link}" ]; then
659 | printf " !!! warning: Unable to determine type (bin/man/etc) for %s\n" "${child_name} @ ${child_link} - skipping" >&2
660 | else
661 | child_link="${alt_root}${child_link_rel}"
662 | local child_value_ref="ALT_GROUP_VALUE_${a}_SLAVE_${s}_VALUE"
663 | local child_value="${!child_value_ref}"
664 | # Did we find a child value?
665 | #
666 | if [[ -n "${child_value}" ]]; then
667 | child_args+=("--slave" "${child_link}" "${child_name}" "${child_value}")
668 | else
669 | printf " !!! warning: Unable to determine type (bin/man/etc) for %s\n" "${child_name} @ ${child_link} - skipping" >&2
670 | fi
671 | fi
672 | ((s += 1))
673 | done
674 |
675 | printf " ... importing alternative %s with priority %s\n" "${alt_value}" "${alt_priority}"
676 | command update-alternatives \
677 | --altdir "${alt_root}/alts" --admindir "${alt_root}/admin" \
678 | --install "${ALT_GROUP_LINK}" "${ALT_GROUP_NAME}" "${alt_value}" "${alt_priority}" \
679 | "${child_args[@]}" \
680 | >/dev/null || return $?
681 |
682 | ((a += 1))
683 | done
684 | # Set current value if manual mode
685 | #
686 | if [ "${ALT_GROUP_STATUS}" = "manual" ] && [ ! "${ALT_GROUP_CURRENT_VALUE:-none}" = "none" ]; then
687 | printf " ... marking %s as active alternative\n" "${ALT_GROUP_CURRENT_VALUE}"
688 | command update-alternatives \
689 | --altdir "${alt_root}/alts" --admindir "${alt_root}/admin" \
690 | --set "${ALT_GROUP_NAME}" "${ALT_GROUP_CURRENT_VALUE}" \
691 | >/dev/null || return $?
692 | fi
693 | # finished
694 | printf "\n"
695 | }
696 |
697 | function help_add() {
698 | cat <<-'USAGE'
699 | usage: my-alternatives add [--child ]...
700 |
701 | adds an alternative to a group within the currently-configured alternatives.
702 | The group is created if it does not already exist.
703 |
704 | NOTE: This is generally a pass-through to 'update-alternatives --install', BUT the argument syntax is slightly diffrent:
705 |
706 | * the order of and arguments are inverted
707 | * uses --child (vs --slave) for secondary files
708 | * arguments should be relative
709 | ex: bin/pager
710 | ex: man/man1/pager.1.gz
711 |
712 | options:
713 |
714 |
715 | the name of the alternative group.
716 | used when referring to the group in other commands.
717 | the name must be unique within the currently-configured alternatives.
718 | ex: pager
719 | ex: pager.1.gz
720 |
721 |
722 | path provided by the group.
723 | path should be relative.
724 | ex: bin/pager
725 | ex: man/man1/pager.1.gz
726 |
727 |
728 | the path of the alternative being introduced.
729 | ex: /usr/bin/less
730 | ex: /usr/share/man/man1/less.1.gz
731 |
732 |
733 | priority of this alternative.
734 | alternatives with larger values have higher priority in automatic mode.
735 |
736 | --child
737 | adds a secondary entry.
738 | optional, but if given, all arguments are required.
739 |
740 | more info:
741 |
742 | see 'my-alternatives --help' to learn more about configuring your own alternatives.
743 |
744 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about the '--install' command.
745 |
746 | USAGE
747 | exit 2
748 | }
749 |
750 | function do_add() {
751 | # Ensure alt_home configured
752 | #
753 | if [ -z "${MY_ALTS_HOME:-}" ]; then
754 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2
755 | exit 2
756 | fi
757 | local alt_root="${MY_ALTS_TMP:-${MY_ALTS_HOME}}"
758 | # Build update-alternatives --install args
759 | #
760 | local args=("--install")
761 | local providing_name providing_path
762 | # primary group-name
763 | #
764 | if [ -z "${1:-}" ]; then
765 | printf "my-alternatives: error: primary group-name not provided. see 'my-alternatives help add' for usage\n" >&2
766 | exit 1
767 | fi
768 | providing_name="${1}"
769 | shift
770 | # primary group-path
771 | #
772 | if [ -z "${1:-}" ]; then
773 | printf "my-alternatives: error: primary group-path not provided. see 'my-alternatives help add' for usage\n" >&2
774 | exit 1
775 | fi
776 | if [[ "${1}" =~ ^/(bin|man)/ ]]; then
777 | providing_path="${alt_root}${1}"
778 | elif [[ "${1}" =~ ^(bin|man)/ ]]; then
779 | providing_path="${alt_root}/${1}"
780 | else
781 | local group_path
782 | group_path="$(get_my_alt_rel_path "${1}")"
783 | if [ "${group_path}" == "${1}" ]; then
784 | printf "my-alternatives: error: unable to determine relative path for group-path: %s\n" "${group_path}" >&2
785 | exit 2
786 | fi
787 | providing_path="${alt_root}${group_path}"
788 | fi
789 | shift
790 |
791 | # add to args (inverted)
792 | args+=("${providing_path}" "${providing_name}")
793 |
794 | # primary alt-path
795 | #
796 | if [ -z "${1:-}" ]; then
797 | printf "my-alternatives: error: primary alt-path not provided. see 'my-alternatives help add' for usage\n" >&2
798 | exit 1
799 | fi
800 | args+=("${1}")
801 | shift
802 | # alt-priority
803 | #
804 | if [ -z "${1:-}" ]; then
805 | printf "my-alternatives: error: alt-priority not provided. see 'my-alternatives help install' for usage\n" >&2
806 | exit 1
807 | fi
808 | args+=("${1}")
809 | shift
810 | # children
811 | #
812 | while (($#)); do
813 | case "${1}" in
814 | --child)
815 | args+=("--slave")
816 | shift
817 | # secondary group-name
818 | #
819 | if [ -z "${1:-}" ]; then
820 | printf "my-alternatives: error: secondary group-name not provided. see 'my-alternatives help add' for usage\n" >&2
821 | exit 1
822 | fi
823 | providing_name="${1}"
824 | shift
825 | # secondary group-path
826 | #
827 | if [ -z "${1:-}" ]; then
828 | printf "my-alternatives: error: secondary group-path not provided. see 'my-alternatives help add' for usage\n" >&2
829 | exit 1
830 | fi
831 | if [[ "${1}" =~ ^/(bin|man)/ ]]; then
832 | providing_path="${alt_root}${1}"
833 | elif [[ "${1}" =~ ^(bin|man)/ ]]; then
834 | providing_path="${alt_root}/${1}"
835 | else
836 | local group_path
837 | group_path="$(get_my_alt_rel_path "${1}")"
838 | if [ "${group_path}" == "${1}" ]; then
839 | printf "my-alternatives: error: unable to determine relative path for group-path: %s\n" "${group_path}" >&2
840 | exit 2
841 | fi
842 | providing_path="${alt_root}${group_path}"
843 | fi
844 | shift
845 |
846 | # add to args (inverted)
847 | args+=("${providing_path}" "${providing_name}")
848 |
849 | # secondary alt-path
850 | #
851 | if [ -z "${1:-}" ]; then
852 | printf "my-alternatives: error: secondary alt-path not provided. see 'my-alternatives help add' for usage\n" >&2
853 | exit 1
854 | fi
855 | args+=("${1}")
856 | shift
857 | ;;
858 | *)
859 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help add' for usage\n" "${1}" >&2
860 | exit 2
861 | ;;
862 | esac
863 | done
864 |
865 | command update-alternatives \
866 | --altdir "${alt_root}/alts" --admindir "${alt_root}/admin" \
867 | "${args[@]}" \
868 | >/dev/null || return $?
869 | }
870 |
871 | function help_select() {
872 | cat <<-'USAGE'
873 | usage: my-alternatives