[gnome-builder/wip/chergert/rustup] wip: stub out idea for rustup plugin



commit bd43e97b5b67556a68be6b6e941a4410b18efe71
Author: Christian Hergert <chergert redhat com>
Date:   Sun Oct 30 02:15:09 2016 -0700

    wip: stub out idea for rustup plugin
    
    If you'd like to work on this and finish it, please contact me!

 configure.ac                                       |    2 +
 plugins/rustup/Makefile.am                         |   30 +
 plugins/rustup/configure.ac                        |   12 +
 .../org.gnome.builder.plugins.rustup.gschema.xml   |   19 +
 plugins/rustup/rustup.plugin                       |    9 +
 plugins/rustup/rustup_plugin/__init__.py           |  198 ++
 plugins/rustup/rustup_plugin/resources/rustup.sh   | 1998 ++++++++++++++++++++
 7 files changed, 2268 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index e0bd388..7459456 100644
--- a/configure.ac
+++ b/configure.ac
@@ -311,6 +311,7 @@ m4_include([plugins/python-gi-imports-completion/configure.ac])
 m4_include([plugins/python-pack/configure.ac])
 m4_include([plugins/quick-highlight/configure.ac])
 m4_include([plugins/rust-langserv/configure.ac])
+m4_include([plugins/rustup/configure.ac])
 m4_include([plugins/support/configure.ac])
 m4_include([plugins/symbol-tree/configure.ac])
 m4_include([plugins/sysmon/configure.ac])
@@ -622,6 +623,7 @@ echo "  Python Jedi Autocompletion ........... : ${enable_jedi_plugin}"
 echo "  Python Language Pack ................. : ${enable_python_pack_plugin}"
 echo "  Quick Highlight ...................... : ${enable_quick_highlight_plugin}"
 echo "  Rust Language Server ................. : ${enable_rust_langserv_plugin}"
+echo "  RustUp ............................... : ${enable_rustup_plugin}"
 echo "  Support .............................. : ${enable_support_plugin}"
 echo "  System Monitor ....................... : ${enable_sysmon_plugin}"
 echo "  Sysprof System Profiler .............. : ${enable_sysprof_plugin}"
diff --git a/plugins/rustup/Makefile.am b/plugins/rustup/Makefile.am
new file mode 100644
index 0000000..99a40ff
--- /dev/null
+++ b/plugins/rustup/Makefile.am
@@ -0,0 +1,30 @@
+if ENABLE_RUSTUP_PLUGIN
+
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+dist_plugin_DATA = \
+       rustup.plugin \
+       rustup_plugin/__init__.py \
+       $(NULL)
+
+resourcedir = $(datadir)/gnome-builder/plugins/
+nobase_resource_DATA = \
+       rustup_plugin/resources/rustup.sh \
+       $(NULL)
+
+gsettings_SCHEMAS = \
+       gsettings/org.gnome.builder.plugins.rustup.gschema.xml \
+       $(NULL)
+
+.PRECIOUS: $(gsettings_SCHEMAS)
+
+@GSETTINGS_RULES@
+
+EXTRA_DIST += $(gsettings_SCHEMAS)
+
+endif
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/rustup/configure.ac b/plugins/rustup/configure.ac
new file mode 100644
index 0000000..26edcd0
--- /dev/null
+++ b/plugins/rustup/configure.ac
@@ -0,0 +1,12 @@
+# --enable-rustup-plugin=yes/no
+AC_ARG_ENABLE([rustup-plugin],
+              [AS_HELP_STRING([--enable-rustup-plugin=@<:@yes/no@:>@],
+                              [Build with support for RustUp integration.])],
+              [enable_rustup_plugin=$enableval],
+              [enable_rustup_plugin=yes])
+
+# for if ENABLE_RUSTUP_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_RUSTUP_PLUGIN, test x$enable_rustup_plugin = xyes)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/rustup/Makefile])
diff --git a/plugins/rustup/gsettings/org.gnome.builder.plugins.rustup.gschema.xml 
b/plugins/rustup/gsettings/org.gnome.builder.plugins.rustup.gschema.xml
new file mode 100644
index 0000000..9d2e42a
--- /dev/null
+++ b/plugins/rustup/gsettings/org.gnome.builder.plugins.rustup.gschema.xml
@@ -0,0 +1,19 @@
+<schemalist>
+  <schema id="org.gnome.builder.plugins.rustup" path="/org/gnome/builder/plugins/rustup/" 
gettext-domain="gnome-builder">
+    <key name="auto-update" type="b">
+      <default>false</default>
+      <summary>Auto-update using rustup</summary>
+      <description>If rustup should be used to auto-update your rust installation.</description>
+    </key>
+    <key name="channel" type="s">
+      <default>"stable"</default>
+      <summary>The RustUp channel</summary>
+      <description>The channel that should be used for rustup updates.</description>
+    </key>
+    <key name="prefix" type="s">
+      <default>"~/.local"</default>
+      <summary>Installation prefix</summary>
+      <description>The prefix for installing rust.</description>
+    </key>
+  </schema>
+</schemalist>
diff --git a/plugins/rustup/rustup.plugin b/plugins/rustup/rustup.plugin
new file mode 100644
index 0000000..8a0b677
--- /dev/null
+++ b/plugins/rustup/rustup.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=rustup_plugin
+Name=RustUp
+Loader=python3
+Description=Helps you keep your rust installation updated
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2016 Christian Hergert
+Builtin=true
+Hidden=true
diff --git a/plugins/rustup/rustup_plugin/__init__.py b/plugins/rustup/rustup_plugin/__init__.py
new file mode 100644
index 0000000..1d10d25
--- /dev/null
+++ b/plugins/rustup/rustup_plugin/__init__.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python3
+
+#
+# __init__.py
+#
+# Copyright (C) 2016 Christian Hergert <chergert redhat com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import gi
+import os
+
+gi.require_version('Ide', '1.0')
+gi.require_version('Gtk', '3.0')
+
+from gi.repository import GLib
+from gi.repository import GObject
+from gi.repository import Gio
+from gi.repository import Gtk
+from gi.repository import Ide
+from gi.repository import Peas
+
+_ = Ide.gettext
+
+def get_module_data_path(name):
+    engine = Peas.Engine.get_default()
+    plugin = engine.get_plugin_info('rustup_plugin')
+    data_dir = plugin.get_data_dir()
+    return GLib.build_filenamev([data_dir, name])
+
+class RustupApplicationAddin(GObject.Object, Ide.ApplicationAddin):
+    """
+    The RustupApplicationAddin provides us a single point to manage updates
+    within the Builder process. Our other addins will perform their various
+    actions by communicating with this singleton.
+    """
+    # Our singleton instance
+    instance = None
+
+    # Current updater while we are busy
+    updater = None
+
+    @GObject.Property(type=bool, default=False)
+    def busy(self):
+        return self.updater is not None
+
+    def do_load(self, application):
+        RustupApplicationAddin.instance = self
+
+    def do_unload(self, application):
+        RustupApplicationAddin.instance = None
+
+    def check_update(self, *args):
+        """
+        This function will begin checking the rustup service for updates
+        to Rust. If we find one, we will notify listeners that we have
+        started a new transfer (and they can add it to their particular
+        IdeTransferManager.
+        """
+        settings = Gio.Settings.new('org.gnome.builder.plugins.rustup')
+
+        channel = settings.get_string('channel')
+        prefix = settings.get_string('prefix')
+
+        self.updater = RustupUpdater(channel=channel, prefix=prefix)
+        self.updater.run()
+
+        self.notify("busy")
+
+    def cancel(self):
+        if self.updater:
+            self.updater.cancel()
+
+class RustupUpdater(GObject.Object):
+    """
+    The RustUpdater class handles the process of checking for a new version
+    of rust as well as tracking the transfer progress of the process.
+
+    Compare this to RustupTransfer() which is a wrapper object around the
+    updater so that we can see the transfer in all workbench windows but
+    only a single update process for the process.
+    """
+    channel = GObject.Property(type=str, default='stable')
+    prefix = GObject.Property(type=str, default='~/.local/')
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.cancellable = Gio.Cancellable()
+
+    def cancel(self):
+        self.cancellable.cancel()
+
+    def run(self):
+        rustup_sh_path = get_module_data_path('resources/rustup.sh')
+        prefix = os.path.expanduser(self.props.prefix)
+
+        launcher = Ide.SubprocessLauncher()
+        launcher.set_flags(Gio.SubprocessFlags.STDOUT_PIPE |
+                           Gio.SubprocessFlags.STDERR_MERGE)
+        launcher.set_run_on_host(True)
+        launcher.set_clear_env(False)
+        launcher.push_argv(rustup_sh_path)
+        launcher.push_argv('--disable-sudo')
+        launcher.push_argv('--yes')
+        launcher.push_argv('--save')
+        launcher.push_argv('--channel=' + self.props.channel)
+        launcher.push_argv('--prefix=' + prefix)
+
+        subprocess = launcher.spawn()
+
+        stdout_pipe = subprocess.get_stdout_pipe()
+        data_stream = Gio.DataInputStream.new(stdout_pipe)
+        data_stream.read_line_async(GLib.PRIORITY_DEFAULT, self.cancellable, self._read_line_cb)
+
+        subprocess.wait_check_async(self.cancellable, self._wait_cb)
+
+    def _wait_cb(self, subprocess, result):
+        print('>>> subprocess exited')
+        try:
+            subprocess.wait_check_finish(result)
+            print("Finished cleanly")
+        except Exception as ex:
+            print(ex)
+
+    def _read_line_cb(self, data_stream, result):
+        try:
+            line, length = data_stream.read_line_finish_utf8(result)
+            print(">>>", line)
+            if length > 0:
+                data_stream.read_line_async(GLib.PRIORITY_DEFAULT, self.cancellable, self._read_line_cb)
+        except Exception as ex:
+            print(ex)
+
+class RustupPreferencesAddin(GObject.Object, Ide.PreferencesAddin):
+
+    def do_load(self, preferences):
+        preferences.add_page('sdk', _('SDKs'), 550)
+        preferences.add_list_group('sdk', 'rustup', _('Rust Developer Channel'), Gtk.SelectionMode.SINGLE, 
100)
+
+        updater = Gtk.Button(halign=Gtk.Align.END, label=_("Check for updates"), visible=True)
+        updater.connect('clicked', RustupApplicationAddin.instance.check_update)
+        RustupApplicationAddin.instance.bind_property('busy', updater, 'sensitive',
+            GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN)
+
+        self.ids = [
+            preferences.add_radio('sdk', 'rustup',
+                'org.gnome.builder.plugins.rustup', 'channel', None, "'stable'", _('Stable'),
+                _('The current stable release of Rust, updated every six weeks and backwards-compatible'),
+                None, 0),
+            preferences.add_radio('sdk', 'rustup',
+                'org.gnome.builder.plugins.rustup', 'channel', None, "'beta'", _('Beta'),
+                _('A preview of the upcoming stable release, intended for testing by crate authors. Updated 
every six weeks and as needed'),
+                None, 0),
+            preferences.add_radio('sdk', 'rustup',
+                'org.gnome.builder.plugins.rustup', 'channel', None, "'nightly'", _('Nightly'),
+                _('The current development branch. It includes unstable features that are not available in 
the betas or stable releases.'),
+                None, 0),
+            preferences.add_switch('sdk', 'rustup',
+                'org.gnome.builder.plugins.rustup', 'auto-update', None, "true", _('Automatically Update'),
+                _('Use RustUp to automatically keep your rust installation up to date.'),
+                None, 0),
+            preferences.add_custom('sdk', 'rustup', updater, None, 1000),
+        ]
+
+    def do_unload(self, preferences):
+        if self.ids:
+            for id in self.ids:
+                preferences.remove_id(id)
+
+class RustupTransfer(Ide.Object, Ide.Transfer):
+
+    title = GObject.Property(type=str)
+    icon_name = GObject.Property(type=str)
+    progress = GObject.Property(type=float)
+    status = GObject.Property(type=str)
+
+    def complete(self, task):
+        task.return_boolean(True)
+
+    def do_execute_async(self, cancellable, callback, data):
+        task = Gio.Task.new(self, cancellable, callback)
+        GLib.timeout_add_seconds(10, self.complete, task)
+
+    def do_execute_finish(self, task):
+        return task.propagate_boolean()
+
diff --git a/plugins/rustup/rustup_plugin/resources/rustup.sh 
b/plugins/rustup/rustup_plugin/resources/rustup.sh
new file mode 100755
index 0000000..9c0c477
--- /dev/null
+++ b/plugins/rustup/rustup_plugin/resources/rustup.sh
@@ -0,0 +1,1998 @@
+#!/bin/sh
+# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+# file at the top-level directory of this distribution and at
+# http://rust-lang.org/COPYRIGHT.
+#
+# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+# option. This file may not be copied, modified, or distributed
+# except according to those terms.
+
+# # Coding conventions
+#
+# * globals are `like_this`.
+# * locals are `_like_this`.
+# * exported values are `LIKE_THIS`.
+# * out-of-band return values are put into `RETVAL`.
+#
+# # Error handling
+#
+# Oh, my goodness, error handling. It's terrifying.
+#
+# This doesn't use -e because it makes it hard to control the
+# presentation of and response to errors.
+#
+# `set -u` is on, which means undefined variables are errors.
+# Generally when evaluating a variable that may not exist I'll
+# write `${mystery_variable-}`, which results in "" if the name
+# is undefined.
+#
+# Every command should be expected to return 0 on success, and
+# non-zero on failure. In one case, for `download_and_check`, the
+# error code needs to be interpreted more carefully because there are
+# multiple successful return codes. Additional return values may be
+# passed the `$RETVAL` global or further `RETVAL_FOO` globals as
+# needed.
+#
+# Most commands are executed via wrappers that provide extra diagnostics
+# and error handling: `run`, which prints the command on failure, and
+# returns the error code, `ignore` which does the same, but is used
+# to indicate the error code won't be handled, and `ensure`, which
+# prints the command on failure, and also exits the process.
+#
+# Pass errors on: `run cmd arg1 arg2 || return 1`. `run` will run
+# the command, printing it if it fails; the `|| return 1` passes the
+# error on to the caller. `ensure cmd arg1 arg1`, runs the command,
+# printing it if it fails, and terminating execution.
+#
+# Don't make typos. You just have to be better than that.
+#
+# This code is very careful never to create empty paths. Any time a
+# new string that will be used as a path is produced, it is checked
+# with `assert_nz`. Likewise, pretty much any time a string is
+# constructed via command invocation it needs to be tested against
+# the empty string.
+#
+# Temporary files must be carefully deleted on every error path.
+
+set -u # Undefined variables are errors
+
+main() {
+    assert_cmds
+    set_globals
+    handle_command_line_args "$@"
+}
+
+set_globals() {
+    # Environment sanity checks
+    assert_nz "$HOME" "\$HOME is undefined"
+    assert_nz "$0" "\$0 is undefined"
+
+    # Some constants
+    version=0.0.1
+    metadata_version=1
+
+    # Find the location of the distribution server
+    default_dist_server="https://static.rust-lang.org";
+    insecure_dist_server="http://static-rust-lang-org.s3-website-us-west-1.amazonaws.com";
+    dist_server="${RUSTUP_DIST_SERVER-$default_dist_server}"
+    gpg_available=false
+
+    # Check to see if GNUPG version 2 is installed, falling back to using version 1 by default
+    gpg_exe=gpg
+    if command -v gpg2 > /dev/null 2>&1; then
+        gpg_exe=gpg2
+    fi
+
+    if command -v "$gpg_exe" > /dev/null 2>&1; then
+        gpg_available=true
+    fi
+
+    # The directory on the server containing the dist artifacts
+    rust_dist_dir=dist
+
+    default_channel="stable"
+
+    # Set up the rustup data dir
+    rustup_dir="${RUSTUP_HOME-$HOME/.rustup}"
+    assert_nz "$rustup_dir" "rustup_dir"
+
+    # Install prefix can be set by the environment
+    default_prefix="${RUSTUP_PREFIX-/usr/local}"
+    default_save=false
+    if [ -n "${RUSTUP_SAVE-}" ]; then
+        default_save=true
+    fi
+
+    # Data locations
+    version_file="$rustup_dir/rustup-version"
+    temp_dir="$rustup_dir/tmp"
+    dl_dir="$rustup_dir/dl"
+
+    # Set up the GPG key
+    official_rust_gpg_key="
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1
+
+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
+Mve696B5tlHyc1KxjHR6w9GRsh4=
+=5FXw
+-----END PGP PUBLIC KEY BLOCK-----
+"
+
+    if [ -n "${RUSTUP_GPG_KEY-}" ]; then
+        gpg_key=$(cat "$RUSTUP_GPG_KEY")
+    else
+        gpg_key="$official_rust_gpg_key"
+    fi
+
+    # This is just used by test.sh for testing sha256sum fallback to shasum
+    sha256sum_cmd="${__RUSTUP_MOCK_SHA256SUM-sha256sum}"
+
+    flag_verbose=false
+    flag_yes=false
+
+    if [ -n "${RUSTUP_VERBOSE-}" ]; then
+        flag_verbose=true
+    fi
+}
+
+# Ensuresthat ~/.rustup exists and uses the correct format
+initialize_metadata() {
+    local _disable_sudo="$1"
+
+    verbose_say "checking metadata version"
+
+    if [ "$rustup_dir" = "$HOME" ]; then
+        err "RUSTUP_HOME is the same as HOME. this cannot be correct. aborting"
+    fi
+
+    # This tries to guard against dumb values of RUSTUP_HOME like ~/ since
+    # rustup will delete the entire directory.
+    if [ -e "$rustup_dir" -a ! -e "$version_file" ]; then
+        say "rustup home dir exists at $rustup_dir but version file $version_file does not."
+        say "this may be old rustup metadata, in which case it can be deleted."
+        err "this is very suspicous. aborting."
+    fi
+
+    # Oh, my. We used to encourage people running this script as root,
+    # and that resulted in users' ~/.rustup directories being owned by
+    # root (running `sudo sh` doesn't change $HOME apparently). Now
+    # that we're not running as root, we can't touch our ~/.rustup
+    # directory. Try to fix that.
+    if [ -e "$version_file" ]; then
+        local _can_write=true
+        local _probe_file="$rustup_dir/write-probe"
+        ignore touch "$_probe_file" 2> /dev/null
+        if [ $? != 0 ]; then
+            _can_write=false
+        else
+            ensure rm "$_probe_file"
+        fi
+
+        if [ "$_can_write" = false ]; then
+            say "$rustup_dir is unwritable. it was likely created by a previous rustup run under sudo"
+            if [ "$_disable_sudo" = false ]; then
+                say "deleting it with sudo"
+                run sudo rm -R "$rustup_dir"
+                if [ $? != 0 ]; then
+                    err "unable to delete unwritable $rustup_dir"
+                fi
+            else
+                say_err "not deleting it because of --disable-sudo"
+                err "delete $rustup_dir to continue. aborting"
+            fi
+        fi
+    fi
+
+    ensure mkdir -p "$rustup_dir"
+    rustup_dir="$(abs_path "$rustup_dir")"
+    assert_nz "$rustup_dir" "rustup_dir"
+
+    if [ ! -e "$version_file" ]; then
+        verbose_say "writing metadata version $metadata_version"
+        echo "$metadata_version" > "$version_file"
+        need_ok "failed to write metadata version"
+    else
+        local _current_version="$(ensure cat "$version_file")"
+        assert_nz "$_current_version"
+        verbose_say "got metadata version $_current_version"
+        if [ "$_current_version" != "$metadata_version" ]; then
+            # Wipe the out of date metadata.
+            say "metadata is out of date. deleting."
+            ensure rm -R "$rustup_dir"
+            ensure mkdir -p "$rustup_dir"
+            echo "$metadata_version" > "$version_file"
+            need_ok "failed to write metadata version"
+        fi
+    fi
+}
+
+handle_command_line_args() {
+    local _save="$default_save"
+    local _date=""
+    local _prefix="$default_prefix"
+    local _uninstall=false
+    local _channel=""
+    local _help=false
+    local _revision=""
+    local _spec=""
+    local _update_hash_file=""
+    local _disable_ldconfig=false
+    local _disable_sudo=false
+    local _extra_targets=""
+    local _add_target=""
+    local _list_targets=false
+
+    local _arg
+    for _arg in "$@"; do
+        case "${_arg%%=*}" in
+            --save )
+                _save=true
+                ;;
+
+            --uninstall )
+                _uninstall=true
+                ;;
+
+            -h | --help )
+                _help=true
+                ;;
+
+            --verbose)
+                # verbose is a global flag
+                flag_verbose=true
+                ;;
+
+            --disable-ldconfig)
+                _disable_ldconfig=true
+                ;;
+
+            --disable-sudo)
+                _disable_sudo=true
+                ;;
+
+            -y | --yes)
+                # yes is a global flag
+                flag_yes=true
+                ;;
+
+            --list-available-targets)
+                _list_targets=true
+                ;;
+
+            --version)
+                echo "rustup.sh $version"
+                exit 0
+                ;;
+
+            --prefix)
+                if is_value_arg "$_arg" "prefix"; then
+                    _prefix="$(get_value_arg "$_arg")"
+                fi
+                ;;
+
+            --channel)
+                if is_value_arg "$_arg" "channel"; then
+                  _channel="$(get_value_arg "$_arg")"
+                fi
+                ;;
+
+            --date)
+                if is_value_arg "$_arg" "date"; then
+                  _date="$(get_value_arg "$_arg")"
+                fi
+                ;;
+
+            --revision)
+                if is_value_arg "$_arg" "revision"; then
+                  _revision="$(get_value_arg "$_arg")"
+                fi
+                ;;
+
+            --spec)
+                if is_value_arg "$_arg" "spec"; then
+                  _spec="$(get_value_arg "$_arg")"
+                fi
+                ;;
+
+            --update-hash-file)
+                if is_value_arg "$_arg" "update-hash-file"; then
+                  # This option is used by multirust to short-circuit reinstalls
+                  # when the channel has not been updated by examining a content
+                  # hash in the update-hash-file
+                  _update_hash_file="$(get_value_arg "$_arg")"
+                fi
+                ;;
+
+            --with-target)
+                if is_value_arg "$_arg" "with-target"; then
+                  local _next_extra_target="$(get_value_arg "$_arg")"
+                  _extra_targets="$_extra_targets $_next_extra_target"
+                fi
+                ;;
+
+            --add-target)
+                if is_value_arg "$_arg" "add-target"; then
+                    _add_target="$(get_value_arg "$_arg")"
+                fi
+                ;;
+
+            *)
+                echo "Unknown argument '$_arg', displaying usage:"
+                echo ${_arg%%=*}
+                _help=true
+                ;;
+
+        esac
+
+    done
+
+    if [ "$_help" = true ]; then
+        print_help
+        exit 0
+    fi
+
+    # Try to run `any` command with `sudo` to check we have enough rights
+    ensure maybe_sudo "$_disable_sudo" true
+
+    # Make sure either rust256sum or shasum exists
+    need_shasum_cmd
+
+    # Check that the various toolchain-specifying flags don't conflict
+    if [ -n "$_revision" ]; then
+        if [ -n "$_channel" ]; then
+            err "the --revision flag may not be combined with --channel"
+        fi
+        if [ -n "$_date" ]; then
+            err "the --revision flag may not be combined with --date"
+        fi
+    fi
+
+    if [ -n "$_spec" ]; then
+        if [ -n "$_channel" ]; then
+            err "the --spec flag may not be combined with --channel"
+        fi
+        if [ -n "$_revision" ]; then
+            err "the --spec flag may not be combined with --revision"
+        fi
+    fi
+
+    if [ -n "$_add_target" ]; then
+        if [ -n "$_channel" ]; then
+            err "the --add-target flag may not be combined with --channel"
+        fi
+        if [ -n "$_date" ]; then
+            err "the --add-target flag may not be combined with --date"
+        fi
+        if [ -n "$_spec" ]; then
+            err "the --add-target flag may not be combined with --spec"
+        fi
+        if [ -n "$_revision" ]; then
+            err "the --add-target flag may not be combined with --revision"
+        fi
+    fi
+
+    if [ "$_list_targets" = true ]; then
+        if [ -n "$_channel" ]; then
+            err "the --list-available-targets flag may not be combined with --channel"
+        fi
+        if [ -n "$_date" ]; then
+            err "the --list-available-targets flag may not be combined with --date"
+        fi
+        if [ -n "$_spec" ]; then
+            err "the --list-available-targets flag may not be combined with --spec"
+        fi
+        if [ -n "$_revision" ]; then
+            err "the --list-available-targets flag may not be combined with --revision"
+        fi
+    fi
+    
+    if [ -z "$_channel" -a -z "$_revision" -a -z "$_spec" ]; then
+        _channel="$default_channel"
+    fi
+
+    # Toolchain can be either a channel, channel + date, or an explicit version
+    local _toolchain=""
+    if [ -n "$_channel" ]; then
+        validate_channel "$_channel"
+        _toolchain="$_channel"
+        if [ -n "$_date" ]; then
+            validate_date "$_date"
+            _toolchain="$_toolchain-$_date"
+        fi
+    elif [ -n "$_revision" ]; then
+        _toolchain="$_revision"
+    elif [ -n "$_spec" ]; then
+        _toolchain="$_spec"
+    fi
+    assert_nz "$_toolchain" "toolchain"
+
+    # --add-target is non-interactive
+    if [ -n "$_add_target" ]; then
+        flag_yes=true
+    fi
+    # --list-targets is non-interactive
+    if [ -n "$_list_targets" ]; then
+        flag_yes=true
+    fi
+
+    if [ "$flag_yes" = false ]; then
+        # Running in interactive mode, check that a tty exists
+        check_tty
+
+        # Print the welcome / warning message and wait for confirmation
+        print_welcome_message "$_prefix" "$_uninstall" "$_disable_sudo"
+
+        get_tty_confirmation
+    fi
+
+    # All work is done in the ~/.rustup dir, which will be deleted
+    # afterward if the user doesn't pass --save. *If* ~/.rustup
+    # already exists and they *did not* pass --save, we'll pretend
+    # they did anyway to avoid deleting their data.
+    local _preserve_rustup_dir="$_save"
+    if [ "$_save" = false -a -e "$version_file" ]; then
+        verbose_say "rustup home exists but not asked to save. saving anyway"
+        _preserve_rustup_dir=true
+    fi
+
+    # Make sure our data directory exists and is the right format
+    initialize_metadata "$_disable_sudo"
+
+    # OK, time to do the things
+    local _succeeded=true
+    if [ "$_list_targets" = true ]; then
+        list_targets "$_prefix"
+        if [ $? != 0 ]; then
+            _succeeded=false
+        fi
+    elif [ -n "$_add_target" ]; then
+        add_target_to_install "$_prefix" "$_add_target" "$_save" "$_disable_sudo"
+        if [ $? != 0 ]; then
+            _succeeded=false
+        fi
+    elif [ "$_uninstall" = false ]; then
+        install_toolchain_from_dist "$_toolchain" "$_prefix" "$_save" "$_update_hash_file" \
+                                    "$_disable_ldconfig" "$_disable_sudo" "$_extra_targets"
+        if [ $? != 0 ]; then
+            _succeeded=false
+        fi
+    else
+        remove_toolchain "$_prefix" "$_disable_sudo"
+        if [ $? != 0 ]; then
+            _succeeded=false
+        fi
+    fi
+
+    # Remove the temporary directory.
+    # This will not happen if we hit certain hard errors earlier.
+    if [ "$_preserve_rustup_dir" = false ]; then
+        verbose_say "removing rustup home $rustup_dir"
+        ensure rm -R "$rustup_dir"
+    else
+        verbose_say "leaving rustup home $rustup_dir"
+    fi
+
+    if [ "$_succeeded" = false ]; then
+        exit 1
+    fi
+}
+
+is_value_arg() {
+    local _arg="$1"
+    local _name="$2"
+
+    echo "$_arg" | grep -q -- "--$_name="
+    return $?
+}
+
+get_value_arg() {
+    local _arg="$1"
+
+    echo "$_arg" | cut -f2 -d=
+}
+
+validate_channel() {
+    local _channel="$1"
+
+    case "$_channel" in
+        stable | beta | nightly )
+            ;;
+        * )
+            err "channel must be either 'stable', 'beta', or 'nightly'"
+            ;;
+    esac
+}
+
+validate_date() {
+    local _date="$1"
+
+    case "$_date" in
+        [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] )
+            ;;
+        * )
+            err "date must be in YYYY-MM-DD format"
+            ;;
+    esac
+}
+
+print_welcome_message() {
+    local _prefix="$1"
+    local _uninstall="$2"
+    local _disable_sudo="$3"
+
+    cat <<EOF
+
+Welcome to Rust.
+EOF
+
+    if [ "$_disable_sudo" = false ]; then
+        if [ "$(id -u)" = 0 ]; then
+            cat <<EOF
+
+WARNING: This script appears to be running as root. While it will work
+correctly, it is no longer necessary for rustup.sh to run as root.
+EOF
+        fi
+    fi
+
+
+    if [ "$_uninstall" = false ]; then
+        cat <<EOF
+
+This script will download the Rust compiler and its package manager, Cargo, and
+install them to $_prefix. You may install elsewhere by running this script
+with the --prefix=<path> option.
+EOF
+    else
+        cat <<EOF
+
+This script will uninstall the existing Rust installation at $_prefix.
+EOF
+    fi
+
+    if [ "$_disable_sudo" = false ]; then
+        cat <<EOF
+
+The installer will run under 'sudo' and may ask you for your password. If you do
+not want the script to run 'sudo' then pass it the --disable-sudo flag.
+EOF
+    fi
+
+    if [ "$_uninstall" = false ]; then
+        cat <<EOF
+
+You may uninstall later by running $_prefix/lib/rustlib/uninstall.sh,
+or by running this script again with the --uninstall flag.
+EOF
+    fi
+
+    echo
+}
+
+
+# Updating toolchains
+
+# Returns 0 on success, 1 on error
+install_toolchain_from_dist() {
+    local _toolchain="$1"
+    local _prefix="$2"
+    local _save="$3"
+    local _update_hash_file="$4"
+    local _disable_ldconfig="$5"
+    local _disable_sudo="$6"
+    local _extra_targets="$7"
+
+    # FIXME: Right now installing rust over top of multirust will
+    # result in a broken multirust installation.
+    # This hack tries to avoid that by detecting if multirust is installed,
+    # but I'd rather fix this by having the installers understand negative
+    # dependencies.
+    local _potential_multirust_bin="$_prefix/bin/multirust"
+    if [ -e "$_potential_multirust_bin" ]; then
+        say_err "multirust appears to be installed at the destination, $_potential_multirust_bin"
+        say_err "installing rust over multirust will result in breakage."
+        local _potential_uninstaller="$_prefix/lib/rustlib/uninstall.sh"
+        if [ -e "$_potential_uninstaller" ]; then
+            say_err "consider uninstalling multirust first by running $_potential_uninstaller"
+        fi
+        err "aborting"
+    fi
+
+    if [ "$gpg_available" = true ]; then
+        # disabling https avoids rust#21293
+        say "gpg available. signatures will be verified"
+    else
+        say "gpg not available. signatures will not be verified"
+    fi
+
+    get_architecture || return 1
+    local _arch="$RETVAL"
+    assert_nz "$_arch" "arch"
+
+    # Inspect any existing installation for additional stds that must be upgraded
+    # and add them to the list
+    merge_existing_extra_targets "$_extra_targets" "$_arch" "$_prefix" || return 1
+    _extra_targets="$RETVAL"
+
+    # We're going to fill in this variables by interrogating the channel
+    # metadata. There are two revisions of the channel metadata, v1 was
+    # a very simple list of binaries; v2 has richer information about
+    # the available packages.
+    
+    # The URL of the main installer
+    local _remote_rust_installer
+    # A space-separated list of other things to install
+    local _extra_remote_installers
+
+    local _manifest_to_stash=""
+
+    # First, try to download a v2 manifest, before falling back to v1 codepaths.
+    download_rust_manifest_v2 "$_toolchain"
+    if [ $? = 0 ]; then
+        local _manifest="$RETVAL"
+        assert_nz "$_manifest" "manifest"
+
+        # We'll save the manifest in the install folder for future modifications
+        _manifest_to_stash="$_manifest"
+
+        validate_manifest_v2 "$_manifest"
+        if [ $? != 0 ]; then
+            say_err "failed to validate channel manifest for '$_toolchain'"
+            return 1
+        fi
+
+        determine_remote_rust_installer_location_v2 "$_manifest"
+        if [ $? != 0 ]; then
+            say_err "unable to find installer url in manifest"
+            return 1
+        fi
+        _remote_rust_installer="$RETVAL"
+
+        if [ "$_extra_targets" != "" ]; then
+            # The user has asked for additional standard libraries.
+            # Figure out where they are.
+            determine_remote_std_locations_v2 "$_manifest" "$_extra_targets" || return 1
+            _extra_remote_installers="$RETVAL"
+        else
+            _extra_remote_installers=""
+        fi
+    else
+        verbose_say "unable to find v2 manifest. trying v1"
+
+        if [ "$_extra_targets" != "" ]; then
+            say_err "v1 manifests don't support --with-target"
+            return 1
+        else
+            _extra_remote_installers=""
+        fi
+
+        determine_remote_rust_installer_location "$_toolchain" || return 1
+        _remote_rust_installer="$RETVAL"
+    fi
+
+    assert_nz "$_remote_rust_installer" "remote rust installer"
+    verbose_say "remote rust installer location: $_remote_rust_installer"
+
+    say "downloading toolchain for '$_toolchain'"
+
+    # Download and install rust package
+    download_and_check "$_remote_rust_installer" false "$_update_hash_file"
+    # Hey! I need to check $? twice here, so it has to be
+    # assigned to a named variable, otherwise the second
+    # check against $? will not be what I expect.
+    local _retval=$?
+    if [ "$_retval" = 20 ]; then
+        say "'$_toolchain' is already up to date"
+        # Successful short-circuit using the update-hash
+        return 0
+    fi
+    if [ "$_retval" != 0 ]; then
+        return 1
+    fi
+    local _rust_installer_file="$RETVAL"
+    local _rust_installer_cache="$RETVAL_CACHE"
+    local _rust_update_hash="$RETVAL_UPDATE_HASH"
+    assert_nz "$_rust_installer_file" "rust_installer_file"
+    assert_nz "$_rust_installer_cache" "rust_installer_cache"
+    assert_nz "$_rust_update_hash" "rust_update_hash"
+
+    say "installing toolchain for '$_toolchain'"
+
+    install_toolchain "$_rust_installer_file" "$_prefix" \
+                      "$_disable_ldconfig" "$_disable_sudo" "$_rust_installer_cache" "$_save"
+    if [ $? != 0 ]; then
+        say_err "failed to install toolchain"
+        return 1
+    fi
+
+    # Download and install extra packages
+    # NB: Splitting $_extra_remote_installers on space by not quoting
+    local _extra_remote_installer
+    for _extra_remote_installer in $_extra_remote_installers; do
+        install_extra_component "$_prefix" "$_extra_remote_installer" "$_disable_sudo" "$_save"
+    done
+
+    # Write the update hash of the rust toolchain to file so that,
+    # when invoked by multirust, the toolchain won't be reinstalled.
+    if [ -n "$_update_hash_file" ]; then
+        echo "$_rust_update_hash" > "$_update_hash_file"
+        if [ $? != 0 ]; then
+            say_err "failed to write update hash to file"
+            return 1
+        fi
+    fi
+
+    # Install the manifest for future updates
+    if [ "$_manifest_to_stash" != "" ]; then
+        # Fix for rust-lang/rust#32154. Somehow rustup.sh managed
+        # until today to exist without escaping ~ in prefix. Probably
+        # because it's only ultimately used by the install script,
+        # which is called via sh. This command here though will fail
+        # if prefix contains ~ so run it through `sh` to escape it.
+        local _prefix="$(sh -c "printf '%s' $_prefix")"
+        local _manifest_stash="$_prefix/lib/rustlib/channel-manifest.toml"
+        ensure printf "%s" "$_manifest_to_stash" | \
+            ensure maybe_sudo "$_disable_sudo" sh -c "cat > \"$_manifest_stash\""
+    fi
+}
+
+merge_existing_extra_targets() {
+    local _extra_targets="$1"
+    local _primary_arch="$2"
+    local _prefix="$3"
+
+    local _components_file="$_prefix/lib/rustlib/components"
+
+    if [ ! -e "$_components_file" ]; then
+       RETVAL="$_extra_targets"
+       return 0
+    fi
+
+    local _component
+    while read _component in; do
+        case "$_component" in
+            rust-std-*)
+                # Extract the triple
+                local _arch="$(ensure printf "%s" "$_component" | ensure sed "s/rust-std-//")"
+                assert_nz "$_arch", "arch"
+                # See if we've already got it
+                ignore printf "%s" "$_extra_targets" | grep -q "$_arch"
+                if [ $? = 0 ]; then
+                    verbose_say "already installing extra std component: $_arch"
+                else
+                    # See if it's the primary target
+                    ignore printf "%s" "$_primary_arch" | grep -q "$_arch"
+                    if [ $? = 0 ]; then
+                        verbose_say "already installing extra std component: $_arch"
+                    else
+                        verbose_say "found extra std component: $_arch"
+                        _extra_targets="$_extra_targets $_arch"
+                    fi
+                fi
+                ;;
+            *)
+                ;;
+        esac
+    done < "$_components_file"
+
+    RETVAL="$_extra_targets"
+    return 0
+}
+
+install_toolchain() {
+    local _installer_file="$1"
+    local _prefix="$2"
+    local _disable_ldconfig="$3"
+    local _disable_sudo="$4"
+    local _installer_cache="$5"
+    local _save="$6"
+
+    # Create a temp directory to put the downloaded toolchain
+    make_temp_dir
+    local _workdir="$RETVAL"
+    assert_nz "$_workdir" "workdir"
+    verbose_say "install work dir: $_workdir"
+
+    local _failing=false
+    install_toolchain_with_workdir "$_installer_file" "$_prefix" \
+                                   "$_disable_ldconfig" "$_disable_sudo" "$_workdir"
+    if [ $? != 0 ]; then
+        _failing=true
+    fi
+    local _retval=$?
+
+    run rm -R "$_workdir"
+    if [ $? != 0 ]; then
+        say_err "couldn't delete workdir"
+        _failing=true
+    fi
+
+    # Throw away the cache if not --save
+    if [ "$_save" = false ]; then
+        verbose_say "discarding cache '$_installer_cache'"
+        run rm -R "$_installer_cache"
+        if [ $? != 0 ]; then
+            say_err "couldn't delete cache dir"
+            _failing=true
+        fi
+    fi
+
+    if [ "$_failing" = true ]; then
+        return 1
+    fi
+}
+
+install_toolchain_with_workdir() {
+    local _installer="$1"
+    local _prefix="$2"
+    local _disable_ldconfig="$3"
+    local _disable_sudo="$4"
+    local _workdir="$5"
+
+    local _installer_dir="$_workdir/$(basename "$_installer" | sed s/.tar.gz$//)"
+
+    # Extract the toolchain
+    say "extracting installer"
+    run tar xzf "$_installer" -C "$_workdir"
+    if [ $? != 0 ]; then
+        verbose_say "failed to extract installer"
+        return 1
+    fi
+
+    # Install the toolchain
+    local _toolchain_dir="$_prefix"
+    verbose_say "installing toolchain to '$_toolchain_dir'"
+
+    if [ "$_disable_ldconfig" = false ]; then
+        maybe_sudo "$_disable_sudo" sh "$_installer_dir/install.sh" --prefix="$_toolchain_dir"
+    else
+        maybe_sudo "$_disable_sudo" sh "$_installer_dir/install.sh" --prefix="$_toolchain_dir" 
--disable-ldconfig
+    fi
+    if [ $? != 0 ]; then
+        verbose_say "failed to install toolchain"
+        return 1
+    fi
+
+}
+
+remove_toolchain() {
+    local _prefix="$1"
+    local _disable_sudo="$2"
+    local _uninstall_script="$_prefix/lib/rustlib/uninstall.sh"
+
+    if [ -e "$_uninstall_script" ]; then
+        verbose_say "uninstalling from '$_uninstall_script'"
+        maybe_sudo "$_disable_sudo" sh "$_uninstall_script"
+        if [ $? != 0 ]; then
+            say_err "failed to remove toolchain"
+            return 1;
+        fi
+        say "toolchain '$_toolchain' uninstalled"
+    else
+        say "no toolchain installed at '$_prefix'"
+    fi
+}
+
+add_target_to_install() {
+    local _prefix="$1"
+    local _target="$2"
+    local _save="$3"
+    local _disable_sudo="$4"
+
+    local _manifest_file="$_prefix/lib/rustlib/channel-manifest.toml"
+
+    if [ ! -e "$_manifest_file" ]; then
+        say_err "no channel manifest at '$_manifest_file'"
+        return 1
+    fi
+
+    local _manifest="$(cat "$_manifest_file")"
+
+    determine_remote_std_locations_v2 "$_manifest" "$_target" || return 1
+    local _url="$RETVAL"
+
+    # NB: No quotes around $url - it's a space-separated list with one element. Removing
+    # the quotes to get rid of an extra space
+    install_extra_component "$_prefix" $_url "$_disable_sudo" "$_save"
+}
+
+list_targets() {
+    local _prefix="$1"
+
+    local _manifest_file="$_prefix/lib/rustlib/channel-manifest.toml"
+
+    if [ ! -e "$_manifest_file" ]; then
+        say_err "no channel manifest at '$_manifest_file'"
+        return 1
+    fi
+
+    local _manifest="$(cat "$_manifest_file")"
+
+    toml_find_package_triples  "$_manifest" rust-std
+    if [ $? != 0 ]; then
+        say_err "error searching manifest for targets"
+        return 1
+    fi
+    local _all_stds="$RETVAL"
+
+    # NB: Not quoting to split on space
+    local _std
+    for _std in $_all_stds; do
+        printf "%s\n" "$_std"
+    done
+}
+
+install_extra_component() {
+    local _prefix="$1"
+    local _url="$2"
+    local _disable_sudo="$3"
+    local _save="$4"
+
+    say "downloading extra component from $_url"
+    download_and_check "$_url" false ""
+    # Don't need to check for the second success value since
+    # we didn't pass an update hash file to download_and_check
+    if [ $? != 0 ]; then
+        return 1
+    fi
+    local _extra_installer_file="$RETVAL"
+    local _extra_installer_cache="$RETVAL_CACHE"
+    assert_nz "$_extra_installer_file" "extra_installer_file"
+    assert_nz "$_extra_installer_cache" "extra_installer_cache"
+
+    say "installing extra component from $_extra_installer_file"
+
+    install_toolchain "$_extra_installer_file" "$_prefix" \
+                      "$_disable_ldconfig" "$_disable_sudo" "$_extra_installer_cache" "$_save"
+    if [ $? != 0 ]; then
+        say_err "failed to install component"
+        return 1
+    fi
+}
+
+# Manifest v2 interface
+
+# Returns 0 on success.
+# Returns the manifest as a string in RETVAL
+download_rust_manifest_v2() {
+    local _toolchain="$1"
+
+    verbose_say "dist_server: $dist_server"
+
+    case "$_toolchain" in
+        nightly | beta | stable )
+            local _remote_rust_manifest="$dist_server/$rust_dist_dir/channel-rust-$_toolchain.toml"
+            ;;
+
+        nightly-* | beta-* | stable-* )
+            extract_channel_and_date_from_toolchain "$_toolchain" || return 1
+            local _channel="$RETVAL_CHANNEL"
+            local _date="$RETVAL_DATE"
+            assert_nz "$_channel" "channel"
+            assert_nz "$_date" "date"
+            local _remote_rust_manifest="$dist_server/$rust_dist_dir/$_date/channel-rust-$_channel.toml"
+            ;;
+
+        *)
+            verbose_say "interpreting toolchain spec as explicit version"
+            local _remote_rust_manifest="$dist_server/$rust_dist_dir/channel-rust-$_toolchain.toml"
+            ;;
+
+    esac
+
+    download_manifest "$_toolchain" "rust" "$_remote_rust_manifest" || return 1
+    local _manifest_file="$RETVAL"
+    local _manifest_cache="$RETVAL_CACHE"
+
+    local _manifest="$(cat "$_manifest_file")"
+
+    if [ $? != 0 ]; then
+        say_err "unable to load manifest from disk"
+        run rm -R "$_manifest_cache"
+        return 1
+    fi
+
+    run rm -R "$_manifest_cache" || return 1
+    RETVAL="$_manifest"
+    return 0
+}
+
+validate_manifest_v2() {
+    local _manifest="$1"
+
+    toml_find_manifest_version "$_manifest"
+    if [ $? != 0 ]; then
+        say_err "unable to find manifest version"
+        return 1
+    fi
+    local _manifest_version="$RETVAL"
+    assert_nz "$_manifest_version" "manifest_version"
+
+    case "$_manifest_version" in
+        2 )
+            ;;
+        * )
+            say_err "channel manifest has unknown version: $_manifest_version"
+            return 1
+            ;;
+    esac
+}
+
+determine_remote_rust_installer_location_v2() {
+    local _manifest="$1"
+
+    get_architecture || return 1
+    local _arch="$RETVAL"
+    assert_nz "$_arch" "arch"
+
+    toml_find_package_url "$_manifest" rust "$_arch"
+    if [ $? != 0 ]; then
+        say_err "unable to find rust package url in manifest"
+        return 1
+    fi
+    local _url="$RETVAL"
+    RETVAL="$_url"
+}
+
+determine_remote_std_locations_v2() {
+    local _manifest="$1"
+    local _targets="$2"
+
+    # A space-separated list of URLs of std installers
+    local _urls=""
+
+    # Replace commas with spaces
+    _targets="$(printf "%s" "$_targets" | sed "s/,/ /g")"
+
+    local _target
+    # NB: Purposefully not quoting $_targets to split on space
+    for _target in $_targets; do
+        toml_find_package_url "$_manifest" rust-std "$_target"
+        if [ $? != 0 ]; then
+            say_err "unable to find package url for std, for $_target"
+            return 1
+        fi
+        _urls="$_urls $RETVAL"
+    done
+
+    RETVAL="$_urls"
+}
+
+# Manifest v2 toml parsing
+
+toml_find_package_url() {
+    local _manifest="$1"
+    local _package="$2"
+    local _arch="$3"
+
+    verbose_say "looking for pkg.$_package.target.$_arch in manifest"
+
+    make_temp_dir
+    local _workdir="$RETVAL"
+    assert_nz "$_workdir" "workdir"
+
+    # Put the manifest in a temp file so it can be read back in
+    # Note the \n. This is needed for `read` to read the last line.
+    # I was surprised.
+    local _tmpfile="$_workdir/manifest"
+    ensure printf "%s\n" "$_manifest" > "$_tmpfile"
+
+    local _found_package=false
+    local _found_url=false
+    local _url=""
+    local _line
+    while read _line; do
+        case "$_line" in
+            # First look for the package header
+            *"[pkg.$_package.target.$_arch]"*)
+                verbose_say "found $_package.$_arch section in manifest"
+                if [ "$_found_package" = true ]; then err "found package twice"; fi
+                _found_package=true
+                ;;
+
+             # Then find the url
+             *url*=*)
+                if [ "$_found_package" = true -a "$_found_url" = false ]; then
+                    _url="$(ensure printf "%s" "$_line" | ensure sed 's/.*url.*\"\(.*\)\".*/\1/')"
+                    assert_nz "$_url" "url is empty!"
+                    verbose_say "url: $_url"
+                    _found_url=true
+                fi
+                ;;
+        esac
+    done < "$_tmpfile"
+
+    ensure rm -R "$_workdir"
+
+    if [ "$_url" = "" ]; then
+        return 1
+    fi
+
+    RETVAL="$_url"
+}
+
+toml_find_package_triples() {
+    local _manifest="$1"
+    local _package="$2"
+    
+    make_temp_dir
+    local _workdir="$RETVAL"
+    assert_nz "$_workdir" "workdir"
+
+    local _tmpfile="$_workdir/manifest"
+    ensure printf "%s\n" "$_manifest" > "$_tmpfile"
+
+    local _triples=""
+    local _line
+    while read _line; do
+        case "$_line" in
+            *"[pkg.$_package.target".*"]"*)
+                verbose_say "found $_package in manifest"
+                local _triple="$(ensure printf "%s" "$_line" | ensure sed 
"s/.*pkg\.$_package\.target\.\(.*\)]/\1/")"
+                verbose_say "triple: $_triple"
+                _triples="$_triples $_triple"
+            ;;
+        esac
+    done < "$_tmpfile"
+
+    ensure rm -R "$_workdir"
+
+    RETVAL="$_triples"
+}
+
+toml_find_manifest_version() {
+    local _manifest="$1"
+
+    make_temp_dir
+    local _workdir="$RETVAL"
+    assert_nz "$_workdir" "workdir"
+
+    local _tmpfile="$_workdir/manifest"
+    ensure printf "%s\n" "$_manifest" > "$_tmpfile"
+
+    local _manifest_version=""
+    local _line
+    while read _line; do
+        case "$_line" in
+             *manifest-version*=*)
+                _manifest_version="$(ensure printf "%s" "$_line" | ensure sed 
's/.*manifest-version.*\"\(.*\)\".*/\1/')"
+                assert_nz "$_manifest_version" "manifest_version is empty!"
+                verbose_say "manifest-version: $_manifest_version"
+                ;;
+        esac
+    done < "$_tmpfile"
+
+    ensure rm -R "$_workdir"
+
+    if [ "$_manifest_version" = "" ]; then
+        return 1
+    fi
+
+    RETVAL="$_manifest_version"
+}
+
+# Manifest v1 interface
+
+determine_remote_rust_installer_location() {
+    local _toolchain="$1"
+
+    verbose_say "determining remote rust installer for '$_toolchain'"
+
+    case "$_toolchain" in
+        nightly | beta | stable | nightly-* | beta-* | stable-* )
+            download_rust_manifest "$_toolchain" || return 1
+            local _manifest_file="$RETVAL"
+            assert_nz "$_manifest_file" "manifest file"
+            local _manifest_cache="$RETVAL_CACHE"
+            assert_nz "$_manifest_cache" "manifest cache"
+            get_remote_installer_location_from_manifest "$_toolchain" "$_manifest_file" rust 
"$rust_dist_dir" || return 1
+            RETVAL="$RETVAL"
+            verbose_say "deleting cache dir $_manifest_cache"
+            run rm -R "$_manifest_cache" || return 1
+            ;;
+
+        * )
+            verbose_say "interpreting toolchain spec as explicit version"
+            get_architecture || return 1
+            local _arch="$RETVAL"
+            assert_nz "$_arch" "arch"
+
+            local _file_name="rust-$_toolchain-$_arch.tar.gz"
+            RETVAL="$dist_server/$rust_dist_dir/$_file_name"
+            ;;
+    esac
+}
+
+# Returns 0 on success.
+# Returns the manifest file in RETVAL and its cache dir in RETVAL_CACHE.
+download_rust_manifest() {
+    local _toolchain="$1"
+
+    case "$_toolchain" in
+        nightly | beta | stable )
+            local _remote_rust_manifest="$dist_server/$rust_dist_dir/channel-rust-$_toolchain"
+            ;;
+
+        nightly-* | beta-* | stable-* )
+            extract_channel_and_date_from_toolchain "$_toolchain" || return 1
+            local _channel="$RETVAL_CHANNEL"
+            local _date="$RETVAL_DATE"
+            assert_nz "$_channel" "channel"
+            assert_nz "$_date" "date"
+            local _remote_rust_manifest="$dist_server/$rust_dist_dir/$_date/channel-rust-$_channel"
+            ;;
+
+        *)
+            err "unrecognized toolchain spec: $_toolchain"
+            ;;
+
+    esac
+
+    download_manifest "$_toolchain" "rust" "$_remote_rust_manifest" || return 1
+    RETVAL="$RETVAL"
+    RETVAL_CACHE="$RETVAL_CACHE"
+}
+
+download_manifest()  {
+    local _toolchain="$1"
+    local _name="$2"
+    local _remote_manifest="$3"
+
+    verbose_say "remote $_name manifest: $_remote_manifest"
+
+    say "downloading manifest for '$_toolchain'"
+    # It's not possible for $? = 20 here, because the update_hash_file
+    # param is empty
+    download_and_check "$_remote_manifest" true "" || return 1
+    RETVAL="$RETVAL"
+    RETVAL_CACHE="$RETVAL_CACHE"
+}
+
+get_remote_installer_location_from_manifest() {
+    local _toolchain="$1"
+    local _manifest_file="$2"
+    local _package_name="$3"
+    local _dist_dir="$4"
+
+    if [ ! -e "$_manifest_file" ]; then
+        err "manifest file '$_manifest_file' does not exist"
+    fi
+
+    get_architecture
+    local _arch="$RETVAL"
+    assert_nz "$_arch" "arch"
+
+    local _line
+    while read _line; do
+        # This regex checks for the version in addition to the package name because there
+        # are package names that are substrings of other packages, 'rust-docs' vs. 'rust'.
+        echo "$_line" | egrep "^$_package_name-(nightly|beta|alpha|[0-9]).*$_arch\.tar\.gz" > /dev/null
+        if [ $? = 0 ]; then
+            case "$_toolchain" in
+                nightly | beta | stable )
+                    RETVAL="$dist_server/$_dist_dir/$_line"
+                    ;;
+
+                nightly-* | beta-* | stable-* )
+                    extract_channel_and_date_from_toolchain "$_toolchain" || return 1
+                    local _channel="$RETVAL_CHANNEL"
+                    local _date="$RETVAL_DATE"
+                    assert_nz "$_channel" "channel"
+                    assert_nz "$_date" "date"
+                    RETVAL="$dist_server/$_dist_dir/$_date/$_line"
+                    ;;
+
+                *)
+                    err "unrecognized toolchain spec: $_toolchain"
+                    ;;
+            esac
+            return
+        fi
+    done < "$_manifest_file"
+
+    err "couldn't find remote installer for '$_arch' in manifest"
+}
+
+extract_channel_and_date_from_toolchain() {
+    local _toolchain="$1"
+
+    case "$_toolchain" in
+        nightly-20[0-9][0-9]-[0-9][0-9]-[0-9][0-9] | \
+        beta-20[0-9][0-9]-[0-9][0-9]-[0-9][0-9] | \
+        stable-20[0-9][0-9]-[0-9][0-9]-[0-9][0-9] )
+            local _channel="$(ensure echo "$_toolchain" | ensure cut -d- -f1)"
+            assert_nz "$_channel" "channel"
+            local _date="$(ensure echo "$_toolchain" | ensure cut -d- -f2,3,4)"
+            assert_nz "$_date" "date"
+            RETVAL_CHANNEL="$_channel"
+            RETVAL_DATE="$_date"
+            ;;
+
+        *)
+            err "unrecognized toolchain spec: $_toolchain"
+            ;;
+
+    esac
+}
+
+# Tools
+
+# FIXME: Temp names based on pid need to worry about pid recycling
+make_temp_name() {
+    local _pid="$$"
+    assert_nz "$_pid" "pid"
+
+    local _tmp_number="${NEXT_TMP_NUMBER-0}"
+    local _tmp_name="tmp-$_pid-$_tmp_number"
+    NEXT_TMP_NUMBER=$((_tmp_number + 1))
+    need_ok "failed to create temp number"
+    assert_nz "$NEXT_TMP_NUMBER" "NEXT_TMP_NUMBER"
+    RETVAL="$_tmp_name"
+}
+
+make_temp_dir() {
+    ensure mkdir -p "$temp_dir"
+
+    ensure make_temp_name
+    local _tmp_name="$temp_dir/$RETVAL"
+    ensure mkdir -p "$_tmp_name"
+    RETVAL="$_tmp_name"
+}
+
+# Returns 0 on success, like sha256sum
+check_sums() {
+    local _sumfile="$1"
+
+    # Hackily edit the sha256 file to workaround a bug in the bots' generation of sums
+    make_temp_dir
+    local _workdir="$RETVAL"
+    assert_nz "$_workdir" "workdir"
+
+    sed s/tmp\\/dist\\/.*\\/final\\/// "$_sumfile" > "$_workdir/tmpsums"
+    need_ok "failed to generate temporary checksums"
+
+    local _sumfile_dirname="$(dirname "$_sumfile")"
+    assert_nz "$_sumfile_dirname" "sumfile_dirname"
+    if command -v "$sha256sum_cmd" > /dev/null 2>&1; then
+        (run cd "$_sumfile_dirname" && run "$sha256sum_cmd" -c "$_workdir/tmpsums" > /dev/null)
+    elif command -v shasum > /dev/null 2>&1; then
+        (run cd "$_sumfile_dirname" && run shasum -c -a 256 "$_workdir/tmpsums" > /dev/null)
+    else
+        err "need either sha256sum or shasum"
+    fi
+    local _sum_retval=$?
+
+    run rm -R "$_workdir" || return 1
+
+    return $_sum_retval
+}
+
+# Outputs 40-char sum to stdout
+create_sum() {
+    local _input="$1"
+
+    local _sum="none"
+    if command -v "$sha256sum_cmd" > /dev/null 2>&1; then
+        _sum="$(run "$sha256sum_cmd" "$_input" | run head -c 40)"
+    elif command -v shasum > /dev/null 2>&1; then
+        _sum="$(run shasum -a 256 "$_input" | run head -c 40)"
+    else
+        err "need either sha256sum or shasum"
+    fi
+    local _sum_retval=$?
+    assert_nz "$_sum" "sum"
+
+    ensure printf "$_sum"
+    return  $_sum_retval
+}
+
+need_shasum_cmd() {
+    if ! command -v "$sha256sum_cmd" > /dev/null 2>&1; then
+        if ! command -v shasum > /dev/null 2>&1; then
+            err "need either sha256sum or shasum"
+        else
+            verbose_say "sha256sum not available. falling back to shasum"
+        fi
+    fi
+}
+
+get_architecture() {
+
+    verbose_say "detecting architecture"
+
+    local _ostype="$(uname -s)"
+    local _cputype="$(uname -m)"
+
+    verbose_say "uname -s reports: $_ostype"
+    verbose_say "uname -m reports: $_cputype"
+
+    if [ "$_ostype" = Darwin -a "$_cputype" = i386 ]; then
+        # Darwin `uname -s` lies
+        if sysctl hw.optional.x86_64 | grep -q ': 1'; then
+            local _cputype=x86_64
+        fi
+    fi
+
+    case "$_ostype" in
+
+        Linux)
+            local _ostype=unknown-linux-gnu
+            ;;
+
+        FreeBSD)
+            local _ostype=unknown-freebsd
+            ;;
+
+        DragonFly)
+            local _ostype=unknown-dragonfly
+            ;;
+
+        Darwin)
+            local _ostype=apple-darwin
+            ;;
+
+        MINGW* | MSYS* | CYGWIN*)
+            local _ostype=pc-windows-gnu
+            ;;
+
+        *)
+            err "unrecognized OS type: $_ostype"
+            ;;
+
+    esac
+
+    case "$_cputype" in
+
+        i386 | i486 | i686 | i786 | x86)
+            local _cputype=i686
+            ;;
+
+        xscale | arm)
+            local _cputype=arm
+            ;;
+
+        armv6l)
+            local _cputype=arm
+            local _ostype="${_ostype}eabihf"
+            ;;
+
+        armv7l)
+            local _cputype=armv7
+            local _ostype="${_ostype}eabihf"
+            ;;
+
+        x86_64 | x86-64 | x64 | amd64)
+            local _cputype=x86_64
+            ;;
+
+        *)
+            err "unknown CPU type: $_cputype"
+
+    esac
+
+    # Detect 64-bit linux with 32-bit userland
+    if [ $_ostype = unknown-linux-gnu -a $_cputype = x86_64 ]; then
+        # $SHELL does not exist in standard 'sh', so probably only exists
+        # if configure is running in an interactive bash shell. /usr/bin/env
+        # exists *everywhere*.
+        local _bin_to_probe="${SHELL-bogus_shell}"
+        if [ ! -e "$_bin_to_probe" -a -e "/usr/bin/env" ]; then
+            _bin_to_probe="/usr/bin/env"
+        fi
+        # $SHELL may be not a binary
+        if [ -e "$_bin_to_probe" ]; then
+            file -L "$_bin_to_probe" | grep -q "text"
+            if [ $? = 0 ]; then
+                _bin_to_probe="/usr/bin/env"
+            fi
+        fi
+        if [ -e "$_bin_to_probe" ]; then
+            file -L "$_bin_to_probe" | grep -q "x86[_-]64"
+            if [ $? != 0 ]; then
+                local _cputype=i686
+            fi
+        fi
+    fi
+
+    local _arch="$_cputype-$_ostype"
+    verbose_say "architecture is $_arch"
+
+    RETVAL="$_arch"
+}
+
+check_sig() {
+    local _sig_file="$1"
+    local _quiet="$2"
+
+    if ! command -v "$gpg_exe" > /dev/null 2>&1; then
+        return
+    fi
+
+    make_temp_dir
+    local _workdir="$RETVAL"
+    assert_nz "$_workdir" "workdir"
+    verbose_say "sig work dir: $_workdir"
+
+    echo "$gpg_key" > "$_workdir/key.asc"
+    need_ok "failed to serialize gpg key"
+
+    # Convert the armored key to .gpg format so it works with --keyring
+    verbose_say "converting armored key to gpg"
+    run "$gpg_exe" --no-permission-warning --dearmor "$_workdir/key.asc"
+    if [ $? != 0 ]; then
+        ignore rm -R "$_workdir"
+        return 1
+    fi
+
+    verbose_say "verifying signature '$_sig_file'"
+    local _output="$("$gpg_exe" --no-permission-warning --keyring "$_workdir/key.asc.gpg" --verify 
"$_sig_file" 2>&1)"
+    if [ $? != 0 ]; then
+        ignore echo "$_output"
+        say_err "signature verification failed"
+        ignore rm -R "$_workdir"
+        return 1
+    fi
+
+    if [ "$_quiet" = false -o "$flag_verbose" = true ]; then
+        ensure echo "$_output"
+    fi
+
+    run rm -R "$_workdir" || return 1
+}
+
+# Downloads a remote file, its checksum, and signature and verifies them.
+# Returns 0 on success. Returns the path to the downloaded file in RETVAL,
+# and the path to it's directory in the cache in RETVAL_CACHE.
+#
+# The caller can decide to remove it from the cache by deleting RETVAL_CACHE.
+#
+# A return code of *20* indicates a successful short circuit from the
+# update hash.
+download_and_check() {
+    local _remote_name="$1"
+    local _quiet="$2"
+    local _update_hash_file="$3"
+
+    local _remote_basename="$(basename "$_remote_name")"
+
+    make_temp_dir
+    local _workdir="$RETVAL"
+    assert_nz "$_workdir" "workdir"
+    verbose_say "download work dir: $_workdir"
+
+    download_checksum_for "$_remote_name" "$_workdir/$_remote_basename"
+    if [ $? != 0 ]; then
+        ignore rm -R "$_workdir"
+        return 1
+    fi
+
+    # This is the unique name of the cache, based on the content hash
+    local _cache_name="$(create_sum "$_workdir/$_remote_basename.sha256" | head -c 20)"
+    need_ok "failed to name cache name from checksum"
+    assert_nz "$_cache_name" "cache_name"
+
+    # If the user already has this rev then don't redownload it
+    if [ -n "$_update_hash_file" ]; then
+        # NB: May fail if file does not exist
+        local _update_hash="$(cat "$_update_hash_file" 2> /dev/null)"
+
+        verbose_say "provided update hash: $_update_hash"
+        verbose_say "new update hash: $_cache_name"
+
+        if [ "$_cache_name" = "$_update_hash" ]; then
+            run rm -R "$_workdir" || return 1
+            # NB: Return code 20 is successful here!
+            return 20
+        fi
+    fi
+
+    # Create a cache directory under dl_dir for this download, based off the content hash
+    local _cache_dir="$dl_dir/$_cache_name"
+    verbose_say "cache dir: $_cache_dir"
+    run mkdir -p "$_cache_dir"
+    if [ $? != 0 ]; then
+        say_err "failed to create download directory"
+        ignore rm -R "$_workdir"
+        return 1
+    fi
+
+    # Move the checksum into the cache. -f because the file may
+    # already exist from previous download.
+    verbose_say "moving '$_workdir/$_remote_basename.sha256' to '$_cache_dir/$_remote_basename.sha256'"
+    run mv -f "$_workdir/$_remote_basename.sha256" "$_cache_dir/$_remote_basename.sha256"
+    if [ $? != 0 ]; then
+        say_err "failed to move checksum into download cache"
+        ignore rm -R "$_workdir"
+        ignore rm -R "$_cache_dir"
+        return 1
+    fi
+
+    # Done with the workdir
+    run rm -R "$_workdir"
+    if [ $? != 0 ]; then
+        say_err "couldn't delete workdir '$_workdir'"
+        ignore rm -R "$_cache_dir"
+        return 1
+    fi
+
+    download_file_and_sig "$_remote_name" "$_cache_dir" "$_quiet"
+    if [ $? != 0 ]; then
+        # Leave the cache dir to resume the download later
+        return 1
+    fi
+    check_file_and_sig "$_cache_dir/$_remote_basename" "$_quiet"
+    if [ $? != 0 ]; then
+        # Whatever's in the cache doesn't add up. Delete it.
+        ignore rm -R "$_cache_dir"
+        return 1
+    fi
+
+    RETVAL="$_cache_dir/$_remote_basename"
+    RETVAL_CACHE="$_cache_dir"
+    RETVAL_UPDATE_HASH="$_cache_name"
+}
+
+download_checksum_for() {
+    local _remote_name="$1"
+    local _local_name="$2"
+
+    local _remote_sums="$_remote_name.sha256"
+    local _local_sums="$_local_name.sha256"
+
+    local _remote_basename="$(basename "$_remote_name")"
+    local _remote_sums_basename="$_remote_basename.sha256"
+    assert_nz "$_remote_basename" "remote basename"
+
+    make_temp_dir
+    local _workdir="$RETVAL"
+    assert_nz "$_workdir" "workdir"
+    verbose_say "download work dir: $_workdir"
+
+    verbose_say "downloading '$_remote_sums' to '$_workdir'"
+    (run cd "$_workdir" && run curl -s -f -O "$_remote_sums")
+    if [ $? != 0 ]; then
+        say_err "couldn't download checksum file '$_remote_sums'"
+        ignore rm -R "$_workdir"
+        return 1
+    fi
+
+    verbose_say "moving '$_workdir/$_remote_sums_basename' to '$_local_sums'"
+    run mv -f "$_workdir/$_remote_sums_basename" "$_local_sums"
+    if [ $? != 0 ]; then
+        say_err "couldn't move '$_workdir/$_remote_sums_basename' to '$_local_sums'"
+        ignore rm -R "$_workdir"
+        return 1
+    fi
+
+    run rm -R "$_workdir"
+    if [ $? != 0 ]; then
+        say_err "couldn't delete workdir '$_workdir'"
+        return 1
+    fi
+}
+
+download_file_and_sig() {
+    local _remote_name="$1"
+    local _local_dirname="$2"
+    local _quiet="$3"
+
+    local _remote_basename="$(basename "$_remote_name")"
+    assert_nz "$_remote_basename" "remote basename"
+
+    local _local_name="$_local_dirname/$_remote_basename"
+
+    local _remote_sig="$_remote_name.asc"
+    local _local_sig="$_local_name.asc"
+
+    # curl -C does not seem to work when the file already exists at 100%,
+    # so just delete it and redownload.
+    if [ -e "$_local_sig" ]; then
+        run rm "$_local_sig"
+        if [ $? != 0 ]; then
+            say_err "failed to delete existing local signature for '$_remote_name'"
+            return 1
+        fi
+    fi
+
+    verbose_say "downloading '$_remote_sig' to '$_local_sig'"
+    (run cd "$_local_dirname" && run curl -s -C - -f -O "$_remote_sig")
+    if [ $? != 0 ]; then
+        say_err "couldn't download signature file '$_remote_sig'"
+        return 1
+    fi
+
+    # Again, because curl -C doesn't like a complete file, short circuit
+    # curl by checking the sum.
+    local _local_sums_file="$_local_dirname/$_remote_basename.sha256"
+    # Throwing away error text since this error is expected.
+    check_sums "$_local_sums_file" > /dev/null 2>&1
+    if [ $? = 0 ]; then
+        verbose_say "checksum good. download already complete"
+        return 0
+    fi
+
+    verbose_say "downloading '$_remote_name' to '$_local_name'"
+    # Invoke curl in a way that will resume if necessary
+    if [ "$_quiet" = false ]; then
+        (run cd "$_local_dirname" && run curl -# -C - -f -O "$_remote_name")
+    else
+        (run cd "$_local_dirname" && run curl -s -C - -f -O "$_remote_name")
+    fi
+    if [ $? != 0 ]; then
+        say_err "couldn't download '$_remote_name'"
+        return 1
+    fi
+}
+
+check_file_and_sig() {
+    local _local_name="$1"
+    local _quiet="$2"
+
+    local _local_sums="$_local_name.sha256"
+    local _local_sig="$_local_name.asc"
+
+    verbose_say "verifying checksums for '$_local_name'"
+    check_sums "$_local_sums"
+    if [ $? != 0 ]; then
+        say_err "checksum failed for '$_local_name'"
+        return 1
+    fi
+
+    check_sig "$_local_sig" "$_quiet"
+    if [ $? != 0 ]; then
+        say_err "signature failed for '$_local_name'"
+        return 1
+    fi
+}
+
+# Verifies that the terminal can be opened or exits
+check_tty() {
+    # FIXME: This isn't sufficient since it just checks that tty
+    # exists, not that it can be read
+    if [ ! -e /dev/tty ]; then
+        err "running in interactive mode (without -y), but cannot open /dev/tty"
+    fi
+}
+
+# Waits for a y/n response and exits if n
+get_tty_confirmation() {
+    local _yn=""
+    read -p "Continue? (y/N) " _yn < /dev/tty
+    need_ok "failed to read from /dev/tty"
+
+    echo
+
+    if [ "$_yn" != "y" -a "$_yn" != "Y" -a "$_yn" != "yes" ]; then
+        say "cancelling"
+        exit 0
+    fi
+}
+
+maybe_sudo() {
+    local _disable_sudo="$1"
+
+    shift
+
+    get_architecture || return 1
+    local _arch="$RETVAL"
+    assert_nz "$_arch" "arch"
+
+    local _is_windows=false
+    case "$_arch" in
+        *windows*)
+            _is_windows=true
+            ;;
+    esac
+
+    if [ "$_disable_sudo" = false -a "$_is_windows" = false ]; then
+        run sudo "$@"
+    else
+        run "$@"
+    fi
+}
+
+# Help
+
+print_help() {
+echo '
+Usage: rustup.sh [--verbose]
+
+Options:
+
+     --channel=(stable|beta|nightly)   Install from channel (default stable)
+     --date=<YYYY-MM-DD>               Install from archives
+     --revision=<version-number>       Install a specific release
+     --spec=<toolchain-spec>           Install from toolchain spec
+     --prefix=<path>                   Install to a specific location (default /usr/local)
+     --uninstall                       Uninstall instead of install
+     --with-target=<triple>            Also install the standard library for the given target
+     --add-target=<triple>             Updates an existing installation with a new target
+     --list-available-targets          Lists targets available to an existing installation
+     --disable-ldconfig                Do not run ldconfig on Linux
+     --disable-sudo                    Do not run installer under sudo
+     --save                            Save downloads for future reuse
+     --yes, -y                         Disable the interactive mode
+     --help, -h                        Display usage information
+'
+}
+
+# Standard utilities
+
+say() {
+    echo "rustup: $1"
+}
+
+say_err() {
+    say "$1" >&2
+}
+
+verbose_say() {
+    if [ "$flag_verbose" = true ]; then
+        say "$1"
+    fi
+}
+
+err() {
+    say "$1" >&2
+    exit 1
+}
+
+need_cmd() {
+    if ! command -v "$1" > /dev/null 2>&1
+    then err "need '$1' (command not found)"
+    fi
+}
+
+need_ok() {
+    if [ $? != 0 ]; then err "$1"; fi
+}
+
+assert_nz() {
+    if [ -z "$1" ]; then err "assert_nz $2"; fi
+}
+
+# Run a command that should never fail. If the command fails execution
+# will immediately terminate with an error showing the failing
+# command.
+ensure() {
+    "$@"
+    need_ok "command failed: $*"
+}
+
+# This is just for indicating that commands' results are being
+# intentionally ignored. Usually, because it's being executed
+# as part of error handling.
+ignore() {
+    run "$@"
+}
+
+# Runs a command and prints it to stderr if it fails.
+run() {
+    "$@"
+    local _retval=$?
+    if [ $_retval != 0 ]; then
+        say_err "command failed: $*"
+    fi
+    return $_retval
+}
+
+# Prints the absolute path of a directory to stdout
+abs_path() {
+    local _path="$1"
+    # Unset CDPATH because it causes havok: it makes the destination unpredictable
+    # and triggers 'cd' to print the path to stdout. Route `cd`'s output to /dev/null
+    # for good measure.
+    (unset CDPATH && cd "$_path" > /dev/null && pwd)
+}
+
+assert_cmds() {
+    need_cmd dirname
+    need_cmd basename
+    need_cmd mkdir
+    need_cmd cat
+    need_cmd curl
+    need_cmd mktemp
+    need_cmd rm
+    need_cmd egrep
+    need_cmd grep
+    need_cmd file
+    need_cmd uname
+    need_cmd tar
+    need_cmd sed
+    need_cmd sh
+    need_cmd mv
+    need_cmd awk
+    need_cmd cut
+    need_cmd sort
+    need_cmd date
+    need_cmd head
+    need_cmd printf
+    need_cmd touch
+    need_cmd id
+}
+
+main "$@"
+
+# vim: set noet ts=8 sts=4 sw=4:


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]