shelly-shell 0.5.2

A Rust based Unix style shell with a typed and structured language syntax.

# The prelude of the Shelly standard library. Everything published from here are included in every
# loaded module.


# Helper function to return the min of the list of supplied parameters.
pub fn min($values: Number...): Number | ()
{
    $values.min
}


# Helper function to return the max of the supplied parameters.
pub fn max($values: Number...): Number | ()
{
    $values.max
}


fn pretty_print_none_value($expected: Type): String
{
    match $expected
    {
        Array   => { "[]"    },
        HashMap => { "[:]"   },
        String  => { '""'    },
        Integer => { "0"     },
        Float   => { "0.0"   },
        Boolean => { "false" },
        _       => { "()"    }
    }
}


fn is_key_type($name: String): Boolean
{
    match $name
    {
        "value_type" => { true  },
        "key_type"   => { true  },
        _            => { false }
    }
}


# Append a pretty print for all type's properties method.
pub fn any::print_properties()
{
    # Let the caller know what type we're printing properties for, for clarity.
    echo "Properties for:" $self.type
    echo

    # Whip through the list of keys and figure out how much padding we're going to need to print
    # the values all aligned with each other.
    let $padding: Integer = 0

    for ($key, _) in $self.properties
    {
        $padding = max $padding $key.count
    }

    # Finally print the keys and the values with their assigned padding.
    for ($key, $value) in $self.properties
    {
        if    $key == "value"
           && !$value
        {
            echo ($key.pad $padding) "->" (pretty_print_none_value $self.type)
        }
        else if    $value.type == Type
                && (is_key_type $key)
        {
            echo ($key.pad $padding) "->" $value.name
        }
        else
        {
            echo ($key.pad $padding) "->" $value
        }
    }
}


# Just a quick access method to avoid always having to jump into the chars first.
pub fn String::count(): Integer
{
    $self.chars.count
}


# Pad out the end of the string with the given amount of spaces or characters.
pub fn String::pad($count: Integer, $text: String | ()): String
{
    let $padding = if $text == () { " " } else { $text }
    let $added = ""

    if $self.count < $count
    {
        for _ in 0..($count - $self.count)
        {
            $added = $added + $padding
        }

        $self + $added
    }
    else
    {
        $self
    }
}


# Strip the given prefix from the string if it's there, otherwise return the string unchanged.
pub fn String::strip_prefix($prefix: String): String
{
    if $self.starts_with $prefix
    {
        $self[$prefix.count..]
    }
    else
    {
        $self
    }
}


# Strip the given suffix from the string if it's there, otherwise return the string unchanged.
pub fn String::strip_suffix($suffix: String): String
{
    if $self.ends_with $suffix
    {
        $self[0..($self.count - $suffix.count)]
    }
    else
    {
        $self
    }
}


# Given a list of numbers sum them all up and return the result.
pub fn sum($args: Number...): Number
{
    $args.sum
}


# Take the number at the parameters and sum them all up returning the answer. The original number
# is left untouched.
pub fn Integer::sum($args: Integer...): Integer
{
    $self + $args.sum
}


# Given a float value add it to the rest of the parameters and return the result leaving the
# original untouched.
pub fn Float::sum($args: Float...): Float
{
    $self + $args.sum
}


# Check to see if an array is empty.
pub fn Array::empty(): Boolean
{
    $self.count == 0
}


# Sum up the values of an array if they're numbers or "sum-able."
pub fn Array::sum(): Number
{
    let $value = 0

    for $next in $self
    {
        $value = $value + $next
    }

    $value
}


# Get the minimum value of an array of numbers or "comparable items." Returns the lessest item or
# () if the array is empty.
pub fn Array::min(): $self.value_type | ()
{
    if $self.empty
    {
        return ()
    }

    let $value = $self[0]

    for $next in $self[1..]
    {
        if $next < $value
        {
            $value = $next
        }
    }

    $value
}


# Get the maximum value of an array of numbers or "comparable items." Returns the greatest item or
# () if the array is empty.
pub fn Array::max(): $self.value_type | ()
{
    if $self.empty
    {
        return ()
    }

    let $value = $self[0]

    for $next in $self[1..]
    {
        if $next > $value
        {
            $value = $next
        }
    }

    $value
}


# Let the array be treated as a stack, push a new item to the beginning of the array.
pub fn Array::push($value: $self.value_type)
{
    $self = [$value, $self...]
}


# Push a list of items in the order provided.
pub fn Array::push_many($values: $self.value_type...)
{
    $self = [$values.reverse..., $self...]
}


# Combine this array with another array and return the results as a new array.
pub fn Array::combine($other: [$self.value_type]): [$self.value_type]
{
    [$self..., $other...]
}


# Attempt to pop the first item from the beginning of the stack, return () if the stack is empty.
pub fn Array::pop(): $self.value_type | ()
{
    if $self.empty
    {
        return ()
    }

    let $item = $self[0]
    $self = $self[1..]

    $item
}


# Keep track of the path stack for pushd and popd.
let $paths: [String] = []


# Display the current state of the push/popd stack.
fn echo_paths()
{
    echo "Stack:" $paths.count

    for $path in $paths
    {
        echo ":" $path
    }
}


# Push the current directory onto the directory stack and then cd to the requested directory. If
# called with 0 arguments then we swap the current directory with the one on the TOS.  (If there is
# one.)
#
# The stack is only updated if the cd is successful.
pub fn pushd($path: String | ())
{
    let $swap = $path == ()

    if $swap
    {
        if $paths.empty
        {
            echo "No paths in directory stack."
            return
        }

        $path = $paths[0]
    }

    let $previous = $pwd
    let $result = cd $path

    if $result
    {
        if $swap
        {
            let _ = $paths.pop
        }

        $paths.push $previous
    }

    echo_paths
}


# Pop the top of the directory stack and change back to that directory. If the stack is empty then
# an error is reported and nothing is done.
#
# The stack is only altered if the cd is successful.
pub fn popd()
{
    if $paths.empty
    {
        echo "No paths in directory stack."
        return
    }

    let $path = $paths[0]
    let $result = cd $path

    if $result
    {
        let _ = $paths.pop
    }

    echo_paths
}