<commit_before>#!/bin/sh

# This file is licensed under the BSD-3-Clause license.
# See the AUTHORS and LICENSE files for more information.
#
# website:          http://www.zfsnap.org
# repository:       https://github.com/zfsnap/zfsnap
# bug tracking:     https://github.com/zfsnap/zfsnap/issues

# Put zsh in POSIX mode
[ -n "${ZSH_VERSION-}" ] && emulate -R sh

readonly VERSION='2.0.0.beta2'

# COMMANDS
ZFS_CMD='/sbin/zfs'
ZPOOL_CMD='/sbin/zpool'

# VARIABLES
TTL='1m'                            # default snapshot TTL
VERBOSE='false'                     # Verbose output?
DRY_RUN='false'                     # Dry run?
POOLS=''                            # List of pools
FS_LIST=''                          # List of all ZFS filesystems
SKIP_POOLS=''                       # List of pools to skip

readonly OS=`uname`
readonly DATE_PATTERN='[12][90][0-9][0-9]-[01][0-9]-[0-3][0-9]_[0-2][0-9].[0-5][0-9].[0-5][0-9]'
TEST_MODE=${TEST_MODE:-false}       # When set to "true", Exit won't really exit
TIME_FORMAT='%Y-%m-%d_%H.%M.%S'     # date/time format for snapshot creation and comparison
RETVAL=''                           # used by functions so we can avoid spawning subshells

## HELPER FUNCTIONS
Err() {
    printf '%s\n' "ERROR: $*" >&2
}
Exit() {
    IsTrue "$TEST_MODE" || exit $1
}
Fatal() {
    printf '%s\n' "FATAL: $*" >&2
    exit 1
}
Note() {
    printf '%s\n' "NOTE: $*" >&2
}
Warn() {
    printf '%s\n' "WARNING: $*" >&2
}

# Returns 0 if argument is "false"
IsFalse() {
    IsTrue "$1" && return 1 || return 0
}
# Returns 0 if argument is "true"
IsTrue() {
    case "$1" in
        true)  return 0 ;;
        false) return 1 ;;
        *)     Fatal "'$1' must be true or false." ;;
    esac
}

## ZFSNAP FUNCTIONS

# Convert bytes to human readable format, to a maximum of one decimal place
#   This approach, while admittedly a bit insane, supports shells which don't
#   have 64-bit integers (mksh, I'm looking at you). As an added plus, this
#   approach handles numbers larger than 64-bit signed integers (~16 EiB),
#   though that's not exactly a terribly likely scenario...
#
#   The premise is that floating points are a matter of where the decimal is. By
#   padding/stripping everything to 9 digits in length (the max safe length for
#   32-bit), then enough precision is available to get a reasonably accurate
#   answer. Possible because the answer's max precision is to one decimal place.
#
#   This approach is not perfect and occasionally will be off by one in the
#   right-most digit. This is an acceptable trade-off IMO, as human readable is
#   already inherently "wrong" by virtue of rounding. If the user wants uber
#   precision, ZFS can tell them the Bytes directly.
# Accepts 1 integer
# Retvals human readable size: e.g. 3.2G
BytesToHuman() {
    local num="$1"
    local answer=''
    local t_dels=0 # number of thousands delimiters

    # must be an integer
    ! IsInt "$num" && RETVAL='' && return 1

    # Bytes can skip the abuses which are to follow
    if [ ${#num} -le 4 ] && [ $num -lt 1024 ]; then
        answer=$num
    else
        local magic=0 # denominator to compensate for base-8 vs base-10 math
        local valley8v10='false' # in the valley of base-8 vs base-10 (e.g. 1022KiB)
        t_dels=$(( (${#num} - 1) / 3 ))

        while true; do
            case $t_dels in
                # numbers derived by taking the first 6 digits of 1024^${t_dels}
                1) magic=102400;; 2) magic=104857;; 3) magic=107374;; # 1) KiB 2) MiB 3) GiB
                4) magic=109951;; 5) magic=112589;; 6) magic=115292;; # 4) TiB 5) PiB 6) EiB
            esac
            # check for the awkward valley of base-8 vs base-10 (e.g. 1010MB)
            IsTrue "$valley8v10" || [ ${magic} -eq 102400 ] && break
            [ $(( ${#num} % 3 )) -ne 1 ] || [ ${num%${num#??????}} -ge $magic ] && break

            valley8v10='true'
            t_dels=$(( $t_dels - 1 ))
        done

        # strip $magic to account for length of $num
        [ $(( ${#num} % 3 )) -eq 2 ] && magic=${magic%?}
        [ $(( ${#num} % 3 )) -eq 0 ] && magic=${magic%??}

        # pad/strip to 9 characters - the longest safe length
        while [ ${#num} -lt 9 ]; do
            num="${num}0"
        done
        num=${num%${num#?????????}}

        # calculate answer; format (if needed) to strip excess and insert decimal
        local new_num=$(( $num / $magic ))
        if IsTrue "$valley8v10"; then
            answer=${new_num}
        else
            answer=${new_num%???}
            if [ ${#answer} -lt 3 ]; then # if we have sufficient-ish precision to use a decimal
                local last_three=${new_num#${answer}}
                # add decimal only if it won't be .0
                [ ${last_three%??} -ne 0 ] && answer="${answer}.${last_three%??}"
            fi
        fi
    fi

    # select appropriate unit
    local answer_unit
    for answer_unit in '' K M G T P E; do
        [ $t_dels -eq 0 ] && break
        t_dels=$(( $t_dels - 1 ))
    done

    RETVAL="${answer}${answer_unit}" && return 0<commit_msg>Rewrite BytesToHuman using long division approach<commit_after>#!/bin/sh

# This file is licensed under the BSD-3-Clause license.
# See the AUTHORS and LICENSE files for more information.
#
# website:          http://www.zfsnap.org
# repository:       https://github.com/zfsnap/zfsnap
# bug tracking:     https://github.com/zfsnap/zfsnap/issues

# Put zsh in POSIX mode
[ -n "${ZSH_VERSION-}" ] && emulate -R sh

readonly VERSION='2.0.0.beta2'

# COMMANDS
ZFS_CMD='/sbin/zfs'
ZPOOL_CMD='/sbin/zpool'

# VARIABLES
TTL='1m'                            # default snapshot TTL
VERBOSE='false'                     # Verbose output?
DRY_RUN='false'                     # Dry run?
POOLS=''                            # List of pools
FS_LIST=''                          # List of all ZFS filesystems
SKIP_POOLS=''                       # List of pools to skip

readonly OS=`uname`
readonly DATE_PATTERN='[12][90][0-9][0-9]-[01][0-9]-[0-3][0-9]_[0-2][0-9].[0-5][0-9].[0-5][0-9]'
TEST_MODE=${TEST_MODE:-false}       # When set to "true", Exit won't really exit
TIME_FORMAT='%Y-%m-%d_%H.%M.%S'     # date/time format for snapshot creation and comparison
RETVAL=''                           # used by functions so we can avoid spawning subshells

## HELPER FUNCTIONS
Err() {
    printf '%s\n' "ERROR: $*" >&2
}
Exit() {
    IsTrue "$TEST_MODE" || exit $1
}
Fatal() {
    printf '%s\n' "FATAL: $*" >&2
    exit 1
}
Note() {
    printf '%s\n' "NOTE: $*" >&2
}
Warn() {
    printf '%s\n' "WARNING: $*" >&2
}

# Returns 0 if argument is "false"
IsFalse() {
    IsTrue "$1" && return 1 || return 0
}
# Returns 0 if argument is "true"
IsTrue() {
    case "$1" in
        true)  return 0 ;;
        false) return 1 ;;
        *)     Fatal "'$1' must be true or false." ;;
    esac
}

## ZFSNAP FUNCTIONS

# Convert bytes to human readable format
#   This approach uses a long division approach in order to avoid problem with
#   performing arithmetic on numerators larger than the current shell's int
#   size (many are limited to 32-bit, but even 64-bit is insufficient in some
#   extreme scenarios).
#
#   The previous version of this function would (sometimes) go to one decimal
#   place. This would be a nice feature, but is left unimplemented for now.
# Accepts 1 integer
# Retvals human readable size: e.g. 3G
BytesToHuman() {
    local bytes="$1"
    local denom='1024'
    local answer="$bytes"
    local count=0

    # must be an integer
    ! IsInt "$bytes" && RETVAL='' && return 1

    while [ ${#answer} -gt 9 ] || [ ${answer:-0} -ge $denom ]; do
        local numer="$answer"
        local tmp_answer=''
        local chunk=''
        local first=''

        # as long as there's digits to operate on
        while [ -n "$numer" ]; do
            first=${numer%${numer#?}} # get first digit
            numer=${numer#${first}} # strip off first digit
            chunk="${chunk}${first}"

            if [ ${chunk:-0} -ge $denom ]; then
                tmp_answer="${tmp_answer}$(( $chunk / $denom ))" # append quotient
                chunk=$(( $chunk % $denom )) # assign remainder
            else
                [ -n "$tmp_answer" ] && tmp_answer="${tmp_answer}0" # don't build leading zeros
            fi
            [ $chunk -eq 0 ] && chunk='' # protect against leading zeros
        done
        answer=$tmp_answer
        count=$(( $count + 1 ))
    done

    local unit
    for unit in '' K M G T P E Z; do
        [ $count -eq 0 ] && break
        count=$(( $count - 1 ))
    done

    RETVAL="${answer}${unit}" && return 0
{'fraction_non_alphanumeric': 0.14101521663375538, 'fraction_numerical': 0.028690904866999652, 'mean_word_length': 2.216286888307807, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '312468', 'n_tokens_mistral': 3012, 'n_tokens_neox': 2746, 'n_words': 940}