shelly-shell 0.5.0

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

# Show if the current directory is in a git workspace and if it is show the current version and if
# files have changed since the last commit.
#
# Show nothing if we're not in a git directory.
pub fn git_widget(): String
{
    let $branch: String
    let $result = git rev-parse --abbrev-ref HEAD -> $branch ~-> /dev/null

    if !$result
    {
        return ""
    }

    $branch = $branch.trim_end

    let $status: String
    $result = git status --porcelain=v1 -> $status ~-> /dev/null

    if !$result
    {
        return ""
    }

    let $changed = 0
    for $line in $status.split "\n"
    {
        if $line != "" { $changed = $changed + 1 }
    }

    if $changed == 0
    {
        return "git: ${branch}"
    }

    "git: ${branch}, ${changed} changed"
}


# Show the current time of day on the command line.
pub fn clock_widget(): String
{
    let $clock: String
    let $result = date "+%H:%M" -> $clock ~-> /dev/null

    if !$result
    {
        return ""
    }

    $clock.trim_end
}


# Show the current time of day in twelve hour time.
pub fn clock_12_widget(): String
{
    let $clock: String
    let $result = date "+%I:%M %p" -> $clock ~-> /dev/null

    if !$result
    {
        return ""
    }

    $clock.trim_end
}


# Show how long the last command took to run, but only if it was over a second.
pub fn command_time_widget(): String
{
    if $last_cmd_time == "0:00"
    {
        return ""
    }

    "Took: $last_cmd_time"
}


# Find the nearest workspace without changing the shell's working directory.
fn widget_workspace($files: [String], $directories: [String]): String
{
    let $directory: String
    let $reply = run_process ["pwd", "-P"] [:] -> $directory ~-> /dev/null

    if    ($reply["error"] != ())
       || ($reply["exit_code"] != 0)
    {
        return ""
    }

    $directory = $directory.trim_end

    loop
    {
        for ($kind, $markers) in [["-f", $files], ["-d", $directories]]
        {
            for $marker in $markers
            {
                let $reply = run_process ["test", $kind, "${directory}/${marker}"] [:] ~-> /dev/null

                if    ($reply["error"] == ())
                   && ($reply["exit_code"] == 0)
                {
                    return $directory
                }
            }
        }
        let $parent: String
        let $reply = run_process ["dirname", $directory] [:] -> $parent ~-> /dev/null

        if    ($reply["error"] != ())
           || ($reply["exit_code"] != 0)
        {
            return ""
        }

        $parent = $parent.trim_end

        if $parent == $directory
        {
            return ""
        }

        $directory = $parent
    }
}


# Show the active Python environment only within a Python workspace.
pub fn venv_widget(): String
{
    let $root = widget_workspace ["pyproject.toml", "setup.py", "setup.cfg",
                                  "requirements.txt"] [".venv", "venv"]
    if $root == ""
    {
        return ""
    }

    let $environment: String
    let $reply = run_process ["printenv", "VIRTUAL_ENV"] [:] -> $environment ~-> /dev/null

    if    ($reply["error"] != ())
       || ($reply["exit_code"] != 0)
       || ($environment.trim_end == "")
    {
        return "venv: inactive"
    }

    let $name: String
    let $reply = run_process ["basename", $environment.trim_end] [:] -> $name ~-> /dev/null

    if    ($reply["error"] != ())
       || ($reply["exit_code"] != 0)
    {
        return "venv: inactive"
    }

    $name = $name.trim_end

    "venv: ${name}"
}


# Read version files without letting a failed probe interrupt prompt rendering.
fn widget_node_version($path: String): String
{
    let $text: String
    let $reply = run_process ["cat", $path] [:] -> $text ~-> /dev/null

    if    ($reply["error"] != ())
       || ($reply["exit_code"] != 0)
    {
        return ""
    }

    for $line in $text.split "\n"
    {
        let $version = ($line.split "#")[0].trim

        if $version != ""
        {
            return $version
        }
    }

    ""
}


# Ignore the optional version prefix while preserving the rest of the version.
fn widget_node_normalize($version: String): String
{
    if !($version.starts_with "v")
    {
        return $version
    }

    let $text = ""
    let $first = true

    for $character in $version.chars
    {
        if $first
        {
            $first = false
        }
        else
        {
            $text = "${text}${character}"
        }
    }

    $text
}


# Show expected versus current Node versions only within a Node project.
pub fn node_widget(): String
{
    let $root = widget_workspace ["package.json", ".nvmrc", ".node-version"] []

    if $root == ""
    {
        return ""
    }

    let $expected = widget_node_version "${root}/.nvmrc"

    if $expected == ""
    {
        $expected = widget_node_version "${root}/.node-version"
    }

    let $current: String
    let $reply = run_process ["node", "--version"] [:] -> $current ~-> /dev/null

    if    ($reply["error"] != ())
       || ($reply["exit_code"] != 0)
    {
        if $expected != ""
        {
            return "node: expected ${expected}, current: missing"
        }

        return "node: missing"
    }

    $current = widget_node_normalize $current.trim

    if $expected == ""
    {
        # JSON.parse reads metadata; it does not execute the project's package code.
        let $probe = '*
            try
            {
                const pkg = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));

                if (pkg && pkg.engines && typeof pkg.engines.node === "string")
                {
                    process.stdout.write(pkg.engines.node);
                }
            }
            catch
            {
            }
            *'

        let $reply = run_process ["node", "--eval", $probe,
                                 "${root}/package.json"] [:] -> $expected ~-> /dev/null

        if    ($reply["error"] != ())
           || ($reply["exit_code"] != 0)
        {
            $expected = ""
        }

        $expected = $expected.trim
    }

    let $normalized = widget_node_normalize $expected

    if    ($expected == "")
       || ($normalized == $current)
    {
        return "node: ${current}"
    }

    "node: expected ${expected}, current: ${current}"
}