mirror of
https://github.com/oh-my-fish/oh-my-fish.git
synced 2024-11-25 09:41:40 +08:00
530 lines
15 KiB
Fish
Executable File
530 lines
15 KiB
Fish
Executable File
#!/usr/bin/env fish
|
|
# Oh My Fish installer. See `install --help` for usage.
|
|
|
|
# Set environment options.
|
|
set -q OMF_REPO_URI; or set OMF_REPO_URI "https://github.com/oh-my-fish/oh-my-fish"
|
|
set -q OMF_REPO_BRANCH; or set OMF_REPO_BRANCH "master"
|
|
|
|
|
|
function main
|
|
# Set default settings
|
|
set -q XDG_DATA_HOME
|
|
and set -g OMF_PATH_DEFAULT "$XDG_DATA_HOME/omf"
|
|
or set -g OMF_PATH_DEFAULT "$HOME/.local/share/omf"
|
|
|
|
set -q XDG_CONFIG_HOME
|
|
and set -g CONFIG_PATH "$XDG_CONFIG_HOME"
|
|
or set -g CONFIG_PATH "$HOME/.config"
|
|
|
|
set -g OMF_CONFIG_DEFAULT "$CONFIG_PATH/omf"
|
|
set -g FISH_CONFIG "$CONFIG_PATH/fish"
|
|
|
|
set -g OMF_PATH "$OMF_PATH_DEFAULT"
|
|
set -g OMF_CONFIG "$OMF_CONFIG_DEFAULT"
|
|
|
|
# Ensure repository URL ends with .git
|
|
set OMF_REPO_URI (echo $OMF_REPO_URI | command sed 's/\.git//').git
|
|
|
|
# Parse command args
|
|
while set -q argv[1]
|
|
switch "$argv[1]"
|
|
case --help -h '/?'
|
|
echo "Usage: install [options]
|
|
Install Oh My Fish
|
|
|
|
Options:
|
|
--config=<path> Put config in a specific path (default is $OMF_CONFIG_DEFAULT).
|
|
--help, -h Show this help message.
|
|
--noninteractive Disable interactive questions (assume no, use with --yes to assume yes).
|
|
--offline[=<path>] Offline install, optionally specifying a tar or directory to use.
|
|
--path=<path> Use a specific install path (default is $OMF_PATH_DEFAULT).
|
|
--uninstall Uninstall existing installation instead of installing.
|
|
--yes, -y Assume yes for interactive questions.
|
|
"
|
|
return 0
|
|
|
|
case '--config=*'
|
|
echo "$argv[1]" | command cut -d= -f2 | command sed -e "s#~#$HOME#" | read -g OMF_CONFIG
|
|
|
|
case --noninteractive
|
|
set -g NONINTERACTIVE
|
|
|
|
case --offline
|
|
set -g OFFLINE
|
|
|
|
case '--offline=*'
|
|
set -g OFFLINE
|
|
echo "$argv[1]" | command cut -d= -f2 | command sed -e "s#~#$HOME#" | read -g OFFLINE_PATH
|
|
|
|
case '--path=*'
|
|
echo "$argv[1]" | command cut -d= -f2 | command sed -e "s#~#$HOME#" | read -g OMF_PATH
|
|
|
|
case --uninstall
|
|
set -g UNINSTALL
|
|
|
|
case --yes -y
|
|
set -g ASSUME_YES
|
|
|
|
case '*'
|
|
abort "Unrecognized option '$argv[1]'. Try 'install --help' for usage."
|
|
end
|
|
set -e argv[1]
|
|
end
|
|
|
|
# Ensure the environment meets all of the requirements.
|
|
assert_cmds
|
|
assert_fish_version_compatible 2.2.0
|
|
assert_git_version_compatible 1.9.5
|
|
assert_interactive
|
|
|
|
# If the user wants to uninstall, jump to uninstallation and exit.
|
|
if set -q UNINSTALL
|
|
uninstall_omf
|
|
return
|
|
end
|
|
|
|
# Check if OMF is already installed.
|
|
if test -d "$OMF_PATH"
|
|
if is_install_dir "$OMF_PATH"
|
|
say "Existing installation detected at $OMF_PATH"
|
|
|
|
confirm_yes "Would you like to remove the existing installation?"
|
|
uninstall_omf
|
|
else
|
|
abort "Target directory $OMF_PATH already exists"
|
|
end
|
|
end
|
|
|
|
# Begin the install process.
|
|
install_omf
|
|
|
|
# We made it!
|
|
say "Installation successful!"
|
|
|
|
# Open a brand new shell if we are in interactive mode.
|
|
set -q NONINTERACTIVE
|
|
or exec fish < /dev/tty
|
|
|
|
return 0
|
|
end
|
|
|
|
|
|
# Add an exit hook to display a message if the installer aborts or errors.
|
|
function on_exit -p %self
|
|
if not contains $argv[3] 0 2
|
|
echo -e "\nOh My Fish installation failed.\n\nIf you think that it's a bug, please open an\nissue with the complete installation log here:\n\nhttp://github.com/oh-my-fish/oh-my-fish/issues"
|
|
end
|
|
end
|
|
|
|
|
|
# Installs Oh My Fish.
|
|
function install_omf
|
|
say "Installing Oh My Fish to $OMF_PATH..."
|
|
|
|
# Prepare paths
|
|
command mkdir -p (dirname "$OMF_PATH")
|
|
|
|
# Install step
|
|
if set -q OFFLINE
|
|
install_offline
|
|
else
|
|
install_from_github
|
|
end
|
|
|
|
# Config step
|
|
install_bootstrap
|
|
install_config
|
|
end
|
|
|
|
|
|
# Downloads and installs the framework from GitHub.
|
|
function install_from_github
|
|
say "Cloning $OMF_REPO_BRANCH from $OMF_REPO_URI..."
|
|
|
|
if not command git clone -q --depth 1 -b $OMF_REPO_BRANCH $OMF_REPO_URI "$OMF_PATH"
|
|
abort "Error cloning repository!"
|
|
end
|
|
|
|
set_git_remotes
|
|
end
|
|
|
|
|
|
# Install the framework from an offline copy of the source.
|
|
function install_offline
|
|
# Prepare the path
|
|
if set -q OFFLINE_PATH
|
|
# Make sure the given path exists
|
|
if not test -e "$OFFLINE_PATH"
|
|
abort "Local installation does not exist"
|
|
end
|
|
else
|
|
# If no path was set, check if the installer is running inside of the source directory.
|
|
set -l path (command dirname (command dirname (builtin status -f)))
|
|
|
|
if is_install_dir "$path"
|
|
set OFFLINE_PATH "$path"
|
|
# Try using the current working directory as the source.
|
|
else if is_install_dir "$PWD"
|
|
set OFFLINE_PATH "$PWD"
|
|
else
|
|
# We tried our best.
|
|
abort "Could not find local installation source"
|
|
end
|
|
end
|
|
|
|
# Check if the path is some sort of tar.
|
|
if test -f "$OFFLINE_PATH"
|
|
say "Offline path is a file, assuming tar archive..."
|
|
|
|
command tar -xf "$OFFLINE_PATH" -C "$OMF_PATH"
|
|
or abort "Could not extract tar file $OFFLINE_PATH"
|
|
|
|
return
|
|
end
|
|
|
|
# At this point, path must be a directory.
|
|
if not test -d "$OFFLINE_PATH"
|
|
abort "$OFFLINE_PATH is not a directory"
|
|
end
|
|
|
|
# Make sure the given path is actually the OMF source.
|
|
if not is_install_dir "$OFFLINE_PATH"
|
|
abort "$OFFLINE_PATH is not a valid local installation source"
|
|
end
|
|
|
|
# Copy the source into the install location.
|
|
command cp -r "$OFFLINE_PATH" "$OMF_PATH"
|
|
or abort "Failed to copy source!"
|
|
|
|
# Set up Git remotes only if the offline install is a Git repository.
|
|
test -d "$OMF_PATH/.git"
|
|
and set_git_remotes
|
|
|
|
return 0
|
|
end
|
|
|
|
|
|
# Set upstream remotes on the framework Git repository.
|
|
function set_git_remotes
|
|
set git_upstream (command git --git-dir "$OMF_PATH/.git" --work-tree "$OMF_PATH" config remote.upstream.url)
|
|
|
|
if test -z "$git_upstream"
|
|
command git --git-dir "$OMF_PATH/.git" --work-tree "$OMF_PATH" remote add upstream $OMF_REPO_URI
|
|
else
|
|
command git --git-dir "$OMF_PATH/.git" --work-tree "$OMF_PATH" remote set-url upstream $OMF_REPO_URI
|
|
end
|
|
end
|
|
|
|
|
|
# Sets up the necessary bootstrap code for Fish to load OMF.
|
|
function install_bootstrap
|
|
set -l fish_config_file "$FISH_CONFIG/config.fish"
|
|
set -l vendor_config_file "$FISH_CONFIG/conf.d/omf.fish"
|
|
|
|
# Create the Fish config directory if it doesn't exist yet (if the first thing the user runs with Fish is this
|
|
# installer, for example).
|
|
command mkdir -p "$FISH_CONFIG"
|
|
|
|
# If Oh My Fish is already configured and ready to go, there's nothing else we need to do here.
|
|
if is_omf_loaded
|
|
return 0
|
|
|
|
# Fish 2.3.0+ supports conf.d which is for vendors to manage, so we can just plop an init file under our control.
|
|
else if is_version_compatible 2.3.0 (get_fish_version)
|
|
say "Writing bootstrap to $vendor_config_file..."
|
|
command mkdir -p "$FISH_CONFIG/conf.d"
|
|
generate_bootstrap > "$vendor_config_file"
|
|
|
|
# If the user doesn't have their own config file, we'll just use that as our bootstrap.
|
|
else if not test -e "$fish_config_file"
|
|
say "Writing bootstrap to $fish_config_file..."
|
|
generate_bootstrap > "$fish_config_file"
|
|
|
|
# Even though config.fish already exists, we can prepend to it if the user is OK with it.
|
|
else if confirm "Would you like Oh My Fish to be added to your configuration automatically?"
|
|
say "Prepending bootstrap to $fish_config_file..."
|
|
|
|
# Create a temporary file to store the combined config so that we can write atomically.
|
|
generate_bootstrap | command cat - "$fish_config_file" > "$fish_config_file.tmp"
|
|
or abort "Error prepending config file"
|
|
|
|
# Swap in the prepended file.
|
|
command mv "$fish_config_file.tmp" "$fish_config_file"
|
|
or abort "Error moving file to $fish_config_file"
|
|
|
|
# We don't have any options left, so let the user set up the bootstrap manually.
|
|
else
|
|
say "For Oh My Fish to work properly, please paste the code below into a file that Fish can run at startup:"
|
|
echo
|
|
generate_bootstrap
|
|
end
|
|
|
|
# Backup the user's theme settings. This can be removed when OMF no longer touches this file to switch themes.
|
|
backup_file "$FISH_CONFIG/functions/fish_prompt.fish"
|
|
end
|
|
|
|
|
|
# Sets up the configuration directory.
|
|
function install_config
|
|
say "Setting up Oh My Fish configuration..."
|
|
|
|
# Set up the Oh My Fish configuration directory.
|
|
if not test -d "$OMF_CONFIG"
|
|
command mkdir -p "$OMF_CONFIG"
|
|
end
|
|
|
|
test -f "$OMF_CONFIG/bundle";
|
|
or echo "theme default" > "$OMF_CONFIG/bundle"
|
|
test -f "$OMF_CONFIG/theme"
|
|
or echo "default" > "$OMF_CONFIG/theme"
|
|
|
|
# Install plugins
|
|
fish -c "omf install"
|
|
or abort "Error installing plugins"
|
|
end
|
|
|
|
|
|
# Generates the bootstrap code used to initialize Oh My Fish on shell startup.
|
|
function generate_bootstrap
|
|
echo "# Path to Oh My Fish install."
|
|
|
|
if test "$OMF_PATH" = "$OMF_PATH_DEFAULT"
|
|
echo "\
|
|
set -q XDG_DATA_HOME
|
|
and set -gx OMF_PATH \"\$XDG_DATA_HOME/omf\"
|
|
or set -gx OMF_PATH \"\$HOME/.local/share/omf\""
|
|
else
|
|
echo "set -gx OMF_PATH '$OMF_PATH'"
|
|
end
|
|
|
|
if test "$OMF_CONFIG" != "$OMF_CONFIG_DEFAULT"
|
|
echo "
|
|
# Customize Oh My Fish configuration path.
|
|
set -gx OMF_CONFIG '$OMF_CONFIG'"
|
|
end
|
|
|
|
echo "
|
|
# Load Oh My Fish configuration.
|
|
source \$OMF_PATH/init.fish"
|
|
end
|
|
|
|
|
|
# Uninstalls an existing OMF installation.
|
|
function uninstall_omf
|
|
is_install_dir "$OMF_PATH"
|
|
or abort "No installation detected at $OMF_PATH"
|
|
|
|
say (set_color -o red ^ /dev/null)"This will uninstall Oh My Fish and all plugins and themes from $OMF_PATH."(set_color normal ^ /dev/null)
|
|
|
|
# If we installed the bootstrap to the user's config, let them know they need to remove it themselves.
|
|
if begin; test -f "$FISH_CONFIG/config.fish"; and grep -q OMF_PATH "$FISH_CONFIG/config.fish"; end
|
|
say (set_color -o ^ /dev/null)"Your configuration will not be modified. You may need to remove Oh My Fish startup code from $FISH_CONFIG/config.fish."(set_color normal ^ /dev/null)
|
|
end
|
|
|
|
confirm_yes "Are you sure you want to continue?"
|
|
say "Uninstalling from $OMF_PATH..."
|
|
|
|
# Trigger package uninstall events
|
|
for path in $OMF_PATH/pkg/*
|
|
set -l package (command basename "$path")
|
|
|
|
test -f "$path/hooks/uninstall.fish"
|
|
and source "$path/hooks/uninstall.fish"
|
|
|
|
test -f "$path/uninstall.fish"
|
|
and source "$path/uninstall.fish"
|
|
|
|
emit uninstall_$package
|
|
end
|
|
|
|
# Remove the core framework
|
|
command rm -rf "$OMF_PATH"
|
|
or abort "Uninstall failed"
|
|
|
|
# Remove the bootstrap if it is managed by us
|
|
set -l vendor_config_file "$FISH_CONFIG/conf.d/omf.fish"
|
|
if test -e "$vendor_config_file"
|
|
command rm "$vendor_config_file"
|
|
or abort "Failed to remove bootstrap file"
|
|
end
|
|
|
|
# Restore backed-up files
|
|
restore_backup_file "$FISH_CONFIG/config.fish"
|
|
restore_backup_file "$FISH_CONFIG/functions/fish_prompt.fish"
|
|
|
|
say "Uninstall complete"
|
|
end
|
|
|
|
|
|
# Makes a backup of a given file.
|
|
function backup_file -a file_path
|
|
test -e "$file_path"; or return 1
|
|
|
|
set -l path (command dirname $file_path)
|
|
set -l file (command basename $file_path)
|
|
set -l name (echo $file | command cut -d. -f1)
|
|
|
|
set -l timestamp (command date +%s)
|
|
set -l backup_file "$path/$name.$timestamp.copy"
|
|
|
|
say "Existent $file found at $path"
|
|
say "↳ Moving to $backup_file"
|
|
|
|
if not command mv "$file_path" $backup_file 2>/dev/null
|
|
abort "Could not backup $file_path"
|
|
end
|
|
|
|
return 0
|
|
end
|
|
|
|
|
|
# Restores a backed-up file to its original location.
|
|
function restore_backup_file -a file_path
|
|
set -l path (command dirname $file_path)
|
|
set -l file (command basename $file_path)
|
|
set -l name (echo $file | cut -d. -f1)
|
|
set -l backup_file_list $path/$name.*.copy
|
|
set -l backup_file_path (echo $backup_file_list | command tr ' ' '\n' | command sort -r | command head -1)
|
|
|
|
if test -e "$backup_file_path"
|
|
say "Restoring backup file to $path/$file"
|
|
command mv "$backup_file_path" "$path/$file" ^/dev/null
|
|
or abort "Could not restore backup $backup_file_path"
|
|
end
|
|
end
|
|
|
|
|
|
# Gets the version of Fish installed.
|
|
function get_fish_version
|
|
if set -q FISH_VERSION
|
|
echo $FISH_VERSION
|
|
else if set -q version
|
|
echo $version
|
|
else
|
|
return 1
|
|
end
|
|
end
|
|
|
|
|
|
# Gets the version of Git installed.
|
|
function get_git_version
|
|
type -f -q git
|
|
and command git --version | command cut -d' ' -f3
|
|
end
|
|
|
|
|
|
# Checks if a path looks like an OMF install.
|
|
function is_install_dir -a path
|
|
test -n "$path"
|
|
and test -d "$path"
|
|
and test -d "$path/pkg/omf"
|
|
end
|
|
|
|
|
|
# Checks if OMF is set up properly and working.
|
|
function is_omf_loaded
|
|
command fish -c "omf --version" > /dev/null ^&1
|
|
end
|
|
|
|
|
|
# Tests if the right-hand side version is equal to or greater than the left-hand side version.
|
|
function is_version_compatible -a lhs rhs
|
|
# Both arguments must be given.
|
|
set -q argv[2]
|
|
or return 1
|
|
|
|
# Sort the versions to get the lesser one.
|
|
set -l sorted (printf "$lhs\n$rhs\n" | command sort -n -t '.' -k 1,1 -k 2,2 -k 3,3 -k 4,4)
|
|
|
|
# Left-hand side must be the smallest version.
|
|
test "$lhs" = "$sorted[1]"
|
|
end
|
|
|
|
|
|
# Assert that all tools we need are available.
|
|
function assert_cmds
|
|
set -l cmds basename cp cut date dirname fish fold git head mkdir mv rm sed sort tar tr
|
|
|
|
for cmd in $cmds
|
|
type -f -q $cmd
|
|
or abort "Command '$cmd' not found"
|
|
end
|
|
end
|
|
|
|
|
|
# Assert that a minimum required version of Fish is installed.
|
|
function assert_fish_version_compatible -a required_version
|
|
set -l installed_version (get_fish_version)
|
|
and is_version_compatible $required_version $installed_version
|
|
or abort "Fish version $required_version or greater required; you have $installed_version"
|
|
end
|
|
|
|
|
|
# Assert that a minimum required version of Git is installed.
|
|
function assert_git_version_compatible -a required_version
|
|
set -l installed_version (get_git_version)
|
|
and is_version_compatible $required_version $installed_version
|
|
or abort "Git version $required_version or greater required; you have $installed_version"
|
|
end
|
|
|
|
|
|
# Ensures the keyboard is readable if in interactive mode.
|
|
function assert_interactive
|
|
set -q NONINTERACTIVE
|
|
and return
|
|
|
|
test -c /dev/tty -a -r /dev/tty
|
|
and echo -n > /dev/tty ^ /dev/null
|
|
or abort "Running interactively, but can't read from tty (try running with --noninteractive)"
|
|
end
|
|
|
|
|
|
# Print a message to the user.
|
|
function say -a message
|
|
printf "$message\n" | command fold -s -w 80
|
|
end
|
|
|
|
|
|
# Aborts the installer and displays an error.
|
|
function abort -a message code
|
|
if test -z "$code"
|
|
set code 1
|
|
end
|
|
|
|
if test -n "$message"
|
|
printf "%sInstall aborted: $message%s\n" (set_color -o red ^ /dev/null) (set_color normal ^ /dev/null) >&2
|
|
else
|
|
printf "%sInstall aborted%s\n" (set_color -o red ^ /dev/null) (set_color normal ^ /dev/null) >&2
|
|
end
|
|
|
|
exit $code
|
|
end
|
|
|
|
|
|
# Asks the user for confirmation.
|
|
function confirm -a message
|
|
# Return true if we assume yes for all questions.
|
|
set -q ASSUME_YES
|
|
and return 0
|
|
|
|
# Return false if we can't ask the question.
|
|
set -q NONINTERACTIVE
|
|
and return 1
|
|
|
|
printf "%s$message (y/N): %s" (set_color yellow ^ /dev/null) (set_color normal ^ /dev/null)
|
|
read -l answer < /dev/tty
|
|
or abort "Failed to read from tty"
|
|
|
|
not test "$answer" != y -a "$answer" != Y -a "$answer" != yes
|
|
end
|
|
|
|
|
|
# Asks the user for a confirmation or aborts.
|
|
function confirm_yes -a message
|
|
confirm "$message"
|
|
or abort "Canceled by user" 2
|
|
end
|
|
|
|
|
|
main $argv
|