Browse Source

Add linter and fix all linting errors and warnings

alacritty
ViViDboarder 2 years ago
parent
commit
38ad9088cc
  1. 21
      .pre-commit-config.yaml
  2. 1
      TODO.md
  3. 7
      ansible-cookbook
  4. 1
      assets/default/abuse-the-force/atf-target
  5. 3
      assets/default/bin/bak
  6. 6
      assets/default/bin/gh-am
  7. 10
      assets/default/bin/ghp
  8. 2
      assets/default/bin/ghpr
  9. 2
      assets/default/bin/git-bfg
  10. 1
      assets/default/bin/git-branch-name
  11. 9
      assets/default/bin/git-changed
  12. 11
      assets/default/bin/git-monthly
  13. 2
      assets/default/bin/git-wtf
  14. 4
      assets/default/bin/hadolint
  15. 1
      assets/default/bin/haste
  16. 2
      assets/default/bin/mosh-vim-forward
  17. 14
      assets/default/bin/nnotes
  18. 2
      assets/default/bin/pbcopy-remote
  19. 2
      assets/default/bin/reattach-to-user-namespace-tmux
  20. 2
      assets/default/bin/ssh-vim-forward
  21. 4
      assets/default/fish/completions/wunderline.fish
  22. 1
      assets/default/fish/functions/fish_prompt.fish
  23. 5
      assets/default/fish/functions/fzf_extensions.fish
  24. 0
      assets/default/fish/functions/npm_activate.fish
  25. 0
      assets/default/fish/functions/ssh-add-all.fish
  26. 8
      assets/default/git/exclude_global
  27. 1
      helpers/custom
  28. 24
      helpers/default
  29. 2
      helpers/initialize
  30. 1
      master-cookbook
  31. 3
      no-sudo
  32. 1
      recipes/default/abuse-the-force
  33. 1
      recipes/default/bin
  34. 2
      recipes/default/build-vim
  35. 1
      recipes/default/force-cli
  36. 1
      recipes/default/golang
  37. 1
      recipes/default/mac-bin
  38. 1
      recipes/default/salesforce
  39. 18
      recipes/no-sudo/build-mosh
  40. 26
      recipes/no-sudo/build-tmux
  41. 5
      test-cookbook

21
.pre-commit-config.yaml

@ -0,0 +1,21 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: check-added-large-files
- id: check-toml
- {id: check-yaml, args: [--allow-multiple-documents]}
- id: check-json
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: mixed-line-ending
- id: check-executables-have-shebangs
- id: detect-private-key
- id: check-symlinks
- id: check-case-conflict
- repo: git://github.com/jumanjihouse/pre-commit-hooks
rev: 1.11.0
hooks:
- id: shellcheck

1
TODO.md

@ -8,4 +8,3 @@ It would then be cool to distribute them individually and set them to be install
recipe 'ViViDboarder/vim-settings'
should clone that repo and run the install script inside

7
ansible-cookbook

@ -7,8 +7,9 @@
# Barebones cookbook that accepts args to run single recipes
# Get current directory for future use in links
cd $(dirname $0)
cd "$(dirname "$0")" || { echo "Could not change directory to $0"; exit 1;}
PROJECT_DIR=$(pwd)
export PROJECT_DIR
# Initialization - DO NOT REMOVE
. helpers/initialize
@ -18,9 +19,9 @@ detect_package_manager
init_paths_and_vars
# Get params
COOKBOOK_NAME=$1
export COOKBOOK_NAME=$1
RECIPIE=$2
# SET_INSTALLED=$3
recipe $RECIPIE
recipe "$RECIPIE"
# set_installed $SET_INSTALLED

1
assets/default/abuse-the-force/atf-target

@ -93,4 +93,3 @@ end
AbuseTheForce::Atf_Config.load
# Print the target name
puts AbuseTheForce::Atf_Config.active_target.name

3
assets/default/bin/bak

@ -4,8 +4,7 @@ f="$1"
ext="${f##*.}"
if [[ "$ext" == "bak" ]]; then
cp $f "${f%.*}"
cp "$f" "${f%.*}"
else
cp "$f" "${f}.bak"
fi

6
assets/default/bin/gh-am

@ -5,12 +5,12 @@ set -e
get_patch() {
local pr_url=$1
curl -L $pr_url.patch 2> /dev/null
curl -L "${pr_url}.patch" 2> /dev/null
}
main() {
local pr_url=$1
get_patch $pr_url | git am
get_patch "$pr_url" | git am
}
main $*
main "$1"

10
assets/default/bin/ghp

@ -2,7 +2,7 @@
# Created by ViViDboarder. Please share with attribution
# Function used to take a github http pull URL and automatically convert it to an ssh url
if [[ "$1" == "" ]]; then
echo "Usage"
echo "ghp <url> <branch>"
@ -16,9 +16,11 @@ if [[ "$1" == "" ]]; then
fi
# Expects $* to be in the form of htts://github.com/user/repo branch
URL=${1/http:\/\//git@} # Replace https:// with git@ at the beginning
URL=${URL/\//:} # Replace / with : before the user name
# Replace https:// with git@ at the beginning
URL=${1/http:\/\//git@}
# Replace / with : before the user name
URL=${URL/\//:}
# Echo the command so we can see what we actually executed
echo "git pull --edit --no-ff $URL $2"
git pull --edit --no-ff $URL $2
git pull --edit --no-ff "$URL" "$2"

2
assets/default/bin/ghpr

@ -25,7 +25,7 @@ else
fi
echo "git fetch $REMOTE refs/pull/$PRNUM/head:PR_$PRNUM"
git fetch $REMOTE refs/pull/$PRNUM/head:PR_$PRNUM
git fetch "$REMOTE" "refs/pull/$PRNUM/head:PR_$PRNUM"
echo "Just fetched Pull Request #$PRNUM. To switch to this branch execute:"
echo "git checkout PR_$PRNUM"

2
assets/default/bin/git-bfg

@ -1,3 +1,3 @@
#! /bin/bash
docker run -it --rm -v `pwd`:/data tagplus5/git-bfg $*
docker run -it --rm -v "$(pwd):/data" tagplus5/git-bfg "$@"

1
assets/default/bin/git-branch-name

@ -1,3 +1,4 @@
#!/bin/bash
git rev-parse HEAD >&/dev/null || exit 1
# shellcheck disable=2063
git branch --no-color | grep '^*' | cut -c 3-

9
assets/default/bin/git-changed

@ -2,7 +2,7 @@
gitismerge () {
local sha=$1
msha=$(git rev-list --merges ${sha}...${sha}~1)
msha=$(git rev-list --merges "${sha}"..."${sha}"~1)
# If commit returned is the one passed it, it is a merge
[ -z "$msha" ] && return 1
return 0
@ -23,9 +23,8 @@ if [ -z "$1" ]; then
fi
# If a merge, return only changes in that merge
if [ -z "$2" ] && gitismerge $1; then
git diff-tree --no-commit-id --name-only $1
if [ -z "$2" ] && gitismerge "$1"; then
git diff-tree --no-commit-id --name-only "$1"
else
git diff --no-commit-id --name-only $1 $2
git diff --no-commit-id --name-only "$1" "$2"
fi

11
assets/default/bin/git-monthly

@ -5,11 +5,15 @@
# Returns a git log of any commits in the current or given month
##########################
# TODO: Build help text
month=$1
year=$2
if [ -z "$month" ]; then
echo "usage: git-monthly <month> <year>"
echo "eg: git-monthly 4 2019"
exit 1
fi
if [[ "$month" == "" ]]; then
# Get the current month
month=$(date "+%m")
@ -32,5 +36,4 @@ else
fi
# Get the git log between the target month and the next month
git log --before={${nyear}-${nmonth}-1} --after={${year}-${month}-1}
git log --before="{${nyear}-${nmonth}-1}" --after="{${year}-${month}-1}"

2
assets/default/bin/git-wtf

@ -202,7 +202,7 @@ def show b
end
def show_relations b, all_branches
ibs, fbs = all_branches.partition { |name, br| $config["integration-branches"].include?(br[:local_branch]) ||
ibs, fbs = all_branches.partition { |name, br| $config["integration-branches"].include?(br[:local_branch]) ||
$config["integration-branches"].include?(br[:remote_branch]) }
if $config["integration-branches"].include? b[:local_branch]
puts "\nFeature branches:" unless fbs.empty?

4
assets/default/bin/hadolint

@ -5,7 +5,7 @@
version=v1.17.2
docker run -i --rm \
-v `pwd`:/data \
-v "$(pwd):/data:ro" \
-w /data \
hadolint/hadolint:${version}-debian \
hadolint $*
hadolint "$@"

1
assets/default/bin/haste

@ -4,4 +4,3 @@ host="hb.iamthefij.com"
a=$(cat)
curl -X POST -s -d "$a" https://${host}/documents | awk -v host=$host -F '"' '{print "https://"host"/"$4}'

2
assets/default/bin/mosh-vim-forward

@ -3,4 +3,4 @@
# mosh-vim-forward forwards only the VIM_COLOR variable
# to a remote shell and attempts to provide a consistent
# shell experience
mosh $* -- sh -c "VIM_COLOR=$VIM_COLOR $(basename $SHELL)"
mosh "$@" -- sh -c "VIM_COLOR=$VIM_COLOR $(basename "$SHELL")"

14
assets/default/bin/nnotes

@ -4,6 +4,12 @@ NOTES_DIR="$HOME/Nextcloud/Notes"
function usage() {
echo "nnotes expects 0, 1, or 2 arguments"
echo "Edit from root notes dir"
echo " nnotes"
echo "Create a note in root dir"
echo " nnotes <note name>"
echo "Create a note in sub dir"
echo " nnotes <subdir> <note name>"
}
function validate() {
@ -17,10 +23,10 @@ function main() {
validate
# nvim "$NOTES_DIR" -c ":cd $NOTES_DIR"
cd $NOTES_DIR
cd "$NOTES_DIR" || { echo "Could change to notes dir" ; exit 1; }
mkdir -p $CATEGORY
nvim $CATEGORY/$FILE
mkdir -p "$CATEGORY"
nvim "$CATEGORY/$FILE"
}
case $# in
@ -43,4 +49,4 @@ case $# in
;;
esac
main $*
main "$@"

2
assets/default/bin/pbcopy-remote

@ -3,4 +3,4 @@
# Created by ViViDboarder. Please share with attribution
# Reads hostname from arg1 and gets echos the tmux buffer over to to pbcopy
ssh $1 'tmux show-buffer' | pbcopy
ssh "$1" 'tmux show-buffer' | pbcopy

2
assets/default/bin/reattach-to-user-namespace-tmux

@ -1,7 +1,7 @@
#!/usr/bin/env bash
if [[ "$(uname)" = "Darwin" ]]; then
reattach-to-user-namespace $@
reattach-to-user-namespace "$@"
else
exec "$@"
fi

2
assets/default/bin/ssh-vim-forward

@ -3,4 +3,4 @@
# ssh-vim-forward forwards only the VIM_COLOR variable
# to a remote shell and attempts to provide a consistent
# shell experience
ssh -t $* VIM_COLOR=$VIM_COLOR $(basename $SHELL)
ssh -t "$@" VIM_COLOR="$VIM_COLOR" "$(basename "$SHELL")"

4
assets/default/fish/completions/wunderline.fish

@ -64,7 +64,3 @@ complete -f -c wunderline -n '__fish_wunderline_needs_command' -a gc -d 'Delete
complete -f -c wunderline -n '__fish_wunderline_needs_command' -a set-platform -d 'Set your preferred application platform'
complete -f -c wunderline -n '__fish_wunderline_needs_command' -a flush -d 'Flush the application cache'
complete -x -c wunderline -n '__fish_wunderline_needs_command' -a help -d 'Display help for [cmd]'

1
assets/default/fish/functions/fish_prompt.fish

@ -67,4 +67,3 @@ function fish_prompt
echo -n -s $arrow (_hostname) $cwd (_git_info) (_prompt_char) ' '
end

5
assets/default/fish/functions/fzf_extensions.fish

@ -2,8 +2,7 @@
function __fzf_preview
if file --mime "$argv" | grep -q binary
echo "$argv is a binary file"
else
coderay "$argv" ;or cat "$argv" 2> /dev/null | head -250
else
coderay "$argv" ;or cat "$argv" 2> /dev/null | head -250
end
end

0
assets/default/fish/functions/npm_activate.fish

0
assets/default/fish/functions/ssh-add-all.fish

8
assets/default/git/exclude_global

@ -6,7 +6,7 @@
*.exe
*.o
*.so
# Packages #
############
# it's better to unpack these files and commit the raw source
@ -19,13 +19,13 @@
*.rar
*.tar
*.zip
# Logs and databases #
######################
*.log
*.sql
*.sqlite
# OS generated files #
######################
.DS_Store
@ -35,7 +35,7 @@
.Trashes
ehthumbs.db
Thumbs.db
# codekit #
###########
.sass-cache/

1
helpers/custom

@ -51,4 +51,3 @@ function source_dotfile {
add_line "#import $RC_NAME from synced" "$HOME/.$RC_NAME"
add_line "$SOURCE_CMD ~/.${RC_NAME}_sync" "$HOME/.$RC_NAME"
}

24
helpers/default

@ -27,10 +27,12 @@ recipe () {
if [ -f "$cookbook_recipe" ]; then
log "Running recipe '$cookbook_recipe'..." 1
separator
# shellcheck source=/dev/null
. "$cookbook_recipe"
elif [ -f "$default_recipe" ]; then
log "Running recipe '$default_recipe'..." 1
separator
# shellcheck source=/dev/null
. "$default_recipe"
else
error "Could not find recipe for '$CURRENT_RECIPE_NAME'. Fail!"
@ -85,7 +87,7 @@ error () {
# Write one or many empty lines to the screen.
#
spacer () {
if [ $1 ]; then
if [ -n "$1" ]; then
local spaces=$1
else
local spaces=1
@ -109,7 +111,7 @@ noop () {
# You can optionally specify the separator character. Default is '-'.
#
separator () {
if [ $1 ]; then
if [ -n "$1" ]; then
local char=$1
else
local char='-'
@ -143,6 +145,7 @@ sudo_package_update () {
error "Unknown package manager: $PACKAGE_MANAGER"
fi
# shellcheck disable=2181
if [ $? -ne 0 ]; then
error "An error occured while updating packages. Fail!"
else
@ -158,18 +161,23 @@ sudo_package () {
detect_package_manager
if [ "$PACKAGE_MANAGER" == 'apt-get' ]; then
# shellcheck disable=2086,2048
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y --allow $*
elif [ "$PACKAGE_MANAGER" == 'yum' ]; then
# shellcheck disable=2086,2048
sudo yum install -y $*
elif [ "$PACKAGE_MANAGER" == 'port' ]; then
# Unintuitive, but this skips all promps which should assume Y
# shellcheck disable=2086,2048
sudo port -N install $*
elif [ "$PACKAGE_MANAGER" == 'brew' ]; then
# shellcheck disable=2086,2048
brew install $*
else
error "Unknown package manager: $PACKAGE_MANAGER"
fi
# shellcheck disable=2181
if [ $? -ne 0 ]; then
error "An error occured while installing packages. Fail!"
else
@ -264,11 +272,11 @@ link () {
src="$src"
elif [ -f "$cookbook_assets_source" ]; then
src="$cookbook_assets_source"
elif [ -f $default_assets_source ]; then
elif [ -f "$default_assets_source" ]; then
src="$default_assets_source"
elif [ -d "$cookbook_assets_source" ]; then
src="$cookbook_assets_source"
elif [ -d $default_assets_source ]; then
elif [ -d "$default_assets_source" ]; then
src="$default_assets_source"
else
error "Could not find '$1' to link Fail!"
@ -362,6 +370,7 @@ add_line () {
local file="$2"
grep "$line" "$file" > /dev/null 2>&1
# shellcheck disable=2181
if [ $? -ne 0 ]; then
log "Adding '$line' to '$file'..."
echo "$line" >> "$file"
@ -374,16 +383,15 @@ add_line () {
# Write a warning if user is not root.
#
warn_if_not_root () {
uid="$(id -u)" && [ "$uid" = "0" ] ||
{ echo "WARNING: You are NOT running this script as 'root'. You might want to consider that..."; }
[ "$(id -u)" = "0" ] ||
{ echo "WARNING: You are NOT running this script as 'root'. You might want to consider that..."; }
}
#
# Stops the execution of the script if user is not root.
#
fail_if_not_root () {
uid="$(id -u)" && [ "$uid" = "0" ] ||
{ echo "You must run this as 'root'. Exiting."; exit 1; }
[ "$(id -u)" = "0" ] || { echo "You must run this as 'root'. Exiting."; exit 1; }
}
#

2
helpers/initialize

@ -11,4 +11,4 @@ warn_if_not_root
spacer 1; separator "="
echo " BOOTSTRAPPING '$COOKBOOK_NAME'..."
separator "="; spacer 1
separator "="; spacer 1

1
master-cookbook

@ -10,6 +10,7 @@
# Get current directory for future use in links
cd "$(dirname "$0")" || { echo "Could not change directory to $0"; exit 1;}
PROJECT_DIR=$(pwd)
export PROJECT_DIR
detect_package_manager

3
no-sudo

@ -8,8 +8,9 @@
##############################################################
# Get current directory for future use in links
cd $(dirname $0)
cd "$(dirname "$0")" || { echo "Could not change directory to $0"; exit 1;}
PROJECT_DIR=$(pwd)
export PROJECT_DIR
init_paths_and_vars

1
recipes/default/abuse-the-force

@ -10,4 +10,3 @@ cd $WORKSPACE/abuse-the-force && git pull && rake install
for f in $DEFAULT_ASSETS_PATH/* ; do
try_link "$f" "$USER_BIN/$(basename $f)"
done

1
recipes/default/bin

@ -4,4 +4,3 @@
for f in "$DEFAULT_ASSETS_PATH"/* ; do
try_link "$f" "$USER_BIN/$(basename "$f")"
done

2
recipes/default/build-vim

@ -36,4 +36,4 @@ function conf_build_and_install() {
sudo ln -s "$vim_path" "${vim_path:0:(-1)}"
}
(cd $vim_dir && conf_build_and_install)
(cd "$vim_dir" && conf_build_and_install)

1
recipes/default/force-cli

@ -12,4 +12,3 @@ chmod +x $USER_BIN/force
for f in $DEFAULT_ASSETS_PATH/* ; do
try_link "$f" "$USER_BIN/$(basename $f)"
done

1
recipes/default/golang

@ -11,4 +11,3 @@ elif [[ "$UNAME_STR" == "Linux" ]]; then
# Do something
echo "**** Install from $TMP_DIR/go${version}.linux-amd64.tar.gz"
fi

1
recipes/default/mac-bin

@ -5,4 +5,3 @@
for f in "$DEFAULT_ASSETS_PATH"/* ; do
try_link "$f" "$USER_BIN/$(basename "$f")"
done

1
recipes/default/salesforce

@ -21,4 +21,3 @@ fi
for f in "$DEFAULT_ASSETS_PATH"/* ; do
try_link "$f" "$USER_BIN/$(basename "$f")"
done

18
recipes/no-sudo/build-mosh

@ -3,16 +3,17 @@ set -e
read -p "Compile protobuf? [Yn] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
# Get library
v=protobuf-2.5.0
wget https://protobuf.googlecode.com/files/${v}.tar.gz
wget "https://protobuf.googlecode.com/files/${v}.tar.gz"
# Extract library
tar -xvzf ${v}.tar.gz
tar -xvzf "${v}.tar.gz"
cd $v
# Configure and install
./configure --prefix=$LOCAL_PREFIX && make && make check && make install
./configure --prefix="$LOCAL_PREFIX" && make && make check && make install
# shellcheck disable=2181
if [ $? -ne 0 ]; then
echo "Error compiling $v"
exit 1
@ -32,8 +33,9 @@ if [[ $REPLY =~ ^[Yy]$ ]]; then
tar -xvzf ${v}.tar.gz
cd $v
# Configure and install
./configure --with-shared --prefix=$LOCAL_PREFIX && make && make install
./configure --with-shared --prefix="$LOCAL_PREFIX" && make && make install
# shellcheck disable=2181
if [ $? -ne 0 ]; then
echo "Error compiling $v"
exit 1
@ -53,9 +55,11 @@ export PATH=$LOCAL_PREFIX/bin:$PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LOCAL_PREFIX/lib
# Do the configure and install
./autogen.sh
./configure --prefix=$LOCAL_PREFIX && make install
./configure --prefix="$LOCAL_PREFIX" && make install
echo
echo "Make sure the following lines are in your bashrc"
# shellcheck disable=2016
echo "export PATH=$LOCAL_PREFIX/bin:"'$PATH'
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:'"$LOCAL_PREFIX/lib'
# shellcheck disable=2016
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:'"$LOCAL_PREFIX/lib"

26
recipes/no-sudo/build-tmux

@ -21,34 +21,34 @@ libevent_name="libevent-$libevent_version-stable"
ncurses_name="ncurses-$ncurses_version"
# Download source files for tmux, libevent, and ncurses to tmp dir
cd $TMP_DIR
curl -OL https://github.com/tmux/tmux/releases/download/$tmux_full_version/${tmux_name}.tar.gz
curl -O https://cloud.github.com/downloads/libevent/libevent/${libevent_name}.tar.gz
wget -O ${ncurses_name}.tar.gz ftp://ftp.gnu.org/gnu/ncurses/${ncurses_name}.tar.gz
cd "$TMP_DIR"
curl -OL "https://github.com/tmux/tmux/releases/download/$tmux_full_version/${tmux_name}.tar.gz"
curl -O "https://cloud.github.com/downloads/libevent/libevent/${libevent_name}.tar.gz"
wget -O "${ncurses_name}.tar.gz" ftp://ftp.gnu.org/gnu/ncurses/${ncurses_name}.tar.gz
# extract files, configure, and compile
# libevent installation
tar xvzf ${libevent_name}.tar.gz
(cd $libevent_name && ./configure --prefix=$LOCAL_PREFIX --disable-shared && make && make install) || exit 1
tar xvzf "${libevent_name}.tar.gz"
(cd "$libevent_name" && ./configure --prefix="$LOCAL_PREFIX" --disable-shared && make && make install) || exit 1
# ncurses installation
tar xvzf ${ncurses_name}.tar.gz
(cd $ncurses_name && ./configure --prefix=$LOCAL_PREFIX && make && make install) || exit 1
tar xvzf "${ncurses_name}.tar.gz"
(cd "$ncurses_name" && ./configure --prefix="$LOCAL_PREFIX" && make && make install) || exit 1
# tmux installation
tar xvzf ${tmux_name}.tar.gz
tar xvzf "${tmux_name}.tar.gz"
(\
cd ${tmux_name} && \
cd "${tmux_name}" && \
./configure CFLAGS="-I$LOCAL_PREFIX/include -I$LOCAL_PREFIX/include/ncurses" LDFLAGS="-L$LOCAL_PREFIX/lib -L$LOCAL_PREFIX/include/ncurses -L$LOCAL_PREFIX/include" && \
CPPFLAGS="-I$LOCAL_PREFIX/include -I$LOCAL_PREFIX/include/ncurses" LDFLAGS="-static -L$LOCAL_PREFIX/include -L$LOCAL_PREFIX/include/ncurses -L$LOCAL_PREFIX/lib" make && \
cp tmux $LOCAL_PREFIX/bin \
cp tmux "$LOCAL_PREFIX/bin" \
) || exit 1
version=`tmux -V | cut -d ' ' -f 2`
version=$( tmux -V | cut -d ' ' -f 2 )
if [ -z "$version" ]; then
echo
echo "[error] failed to install tmux - check for errors in the above output"
exit 1
fi
cd $ROOT_DIR
cd "$ROOT_DIR"

5
test-cookbook

@ -9,11 +9,12 @@
# Get current directory for future use in links
cd $(dirname $0)
cd "$(dirname "$0")" || { echo "Could not change directory to $0"; exit 1;}
PROJECT_DIR=$(pwd)
export PROJECT_DIR
WORKSPACE=$HOME/workspace
mkdir -p $WORKSPACE
mkdir -p "$WORKSPACE"
if prompt_yn "Install Salesforce tools?" ; then
recipe 'salesforce'

Loading…
Cancel
Save