Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: basic progress bar #1

Merged
merged 5 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ version = "1.0.0"

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
ansi = ">= 0.1.0 and < 1.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
13 changes: 13 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# This file was generated by Gleam
# You typically do not need to edit this file

packages = [
{ name = "ansi", version = "0.1.0", build_tools = ["rebar3"], requirements = [], otp_app = "ansi", source = "hex", outer_checksum = "4BF92B41E29E0480350A3260DC3F7ED52073C56FE236EFCAB1680A5E4C3923A5" },
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
]

[requirements]
ansi = { version = ">= 0.1.0 and < 1.0.0"}
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
25 changes: 23 additions & 2 deletions src/glitzer.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import gleam/io
import glitzer/progress

@external(erlang, "glitzer_ffi", "sleep")
@external(javascript, "./glitzer_ffi.mjs", "sleep")
pub fn sleep(ms: Int) -> a

pub fn main() {
io.println("Hello from glitzer!")
let bar = progress.default_bar()

do_something(bar, 0)
}

fn do_something(bar, count) {
case count < 100 {
True -> {
let bar = case count > 50 {
True -> progress.finish(bar)
False -> progress.tick(bar)
}
progress.print_bar(bar)
sleep(15)
do_something(bar, count + 1)
}
False -> Nil
}
}
11 changes: 11 additions & 0 deletions src/glitzer/codes.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// Print this code to clear the whole screen.
pub const clear_screen_code = "\u{001b}[2J"

/// Print this code to clear the current line.
pub const clear_line_code = "\u{001b}[2K"

/// Print this code to return the cursor to the "home" position.
pub const return_home_code = "\u{001b}[H"

/// Print this code to return to the start of the line
pub const return_line_start_code = "\r"
191 changes: 191 additions & 0 deletions src/glitzer/progress.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import gleam/io
import gleam/string
import gleam/string_builder.{type StringBuilder}

import glitzer/codes

/// A `String` with only one character.
pub opaque type Char {
Char(char: String)
}

/// Create a `Char` from a `String`. Returns a `Char` with the given string if
/// the strings length is equal to one and a `Char` of "#" otherwise.
///
/// <details>
/// <summary>Example:<summary>
///
/// ```gleam
/// import glitzer/progress
///
/// fn example() {
/// let char = progress.char_from_string("A")
/// }
/// ```
pub fn char_from_string(from in: String) -> Char {
let len = string.length(in)

case len {
1 -> Char(in)
_ -> Char("#")
}
}

pub fn string_from_char(from in: Char) -> String {
in.char
}

pub opaque type State {
State(progress: Int)
}

/// The style of a progress bar.
pub opaque type ProgressStyle {
ProgressStyle(
left: String,
right: String,
empty: Char,
fill: Char,
length: Int,
state: State,
)
}

/// Create and return a default style for a progress bar.
pub fn default_bar() -> ProgressStyle {
ProgressStyle(
left: "[",
right: "]",
empty: Char(" "),
fill: Char("#"),
length: 100,
state: State(progress: 0),
)
}

/// Create a new (completely empty) progress bar.
///
/// <details>
/// <summary>Example:<summary>
///
/// ```gleam
/// import glitzer/progress
///
/// fn example() {
/// let bar =
/// progress.new_bar()
/// |> progress.with_length(50)
/// |> progress.with_left_text("Progress: ")
/// |> progress.with_fill(progress.char_from_string("+"))
/// }
/// ```
pub fn new_bar() -> ProgressStyle {
ProgressStyle(
left: "",
right: "",
empty: Char(" "),
fill: Char(" "),
length: 0,
state: State(progress: 0),
)
}

/// Add left text to a progress bar.
pub fn with_left_text(
bar bar: ProgressStyle,
left text: String,
) -> ProgressStyle {
ProgressStyle(..bar, left: text)
}

/// Add right text to a progress bar.
pub fn with_right_text(
bar bar: ProgressStyle,
right text: String,
) -> ProgressStyle {
ProgressStyle(..bar, right: text)
}

/// Add a character to a progress bar that is used to represent the
/// "background" of the bar.
pub fn with_empty(bar bar: ProgressStyle, empty char: Char) -> ProgressStyle {
ProgressStyle(..bar, empty: char)
}

/// Add a character to a progress bar that is used to fill the bar on each
/// tick.
pub fn with_fill(bar bar: ProgressStyle, fill char: Char) -> ProgressStyle {
ProgressStyle(..bar, fill: char)
}

/// Add length to a progress bar.
pub fn with_length(bar bar: ProgressStyle, length len: Int) -> ProgressStyle {
ProgressStyle(..bar, length: len)
}

/// Increase the progress of the bar by one.
pub fn tick(bar bar: ProgressStyle) -> ProgressStyle {
ProgressStyle(..bar, state: State(progress: bar.state.progress + 1))
}

/// Completely fill the progress bar.
pub fn finish(bar bar: ProgressStyle) -> ProgressStyle {
ProgressStyle(..bar, state: State(progress: bar.length + 1))
}

/// Print the progress bar to stderr.
///
/// <details>
/// <summary>Example:<summary>
///
/// ```gleam
/// import glitzer/progress
///
/// fn example() {
/// let bar = progress.default_bar()
///
/// run_example(bar, 0)
/// }
///
/// fn run_example(bar, count) {
/// case count < 100 {
/// True -> {
/// let bar = progress.tick(bar)
/// // do some awesome stuff :3
/// progress.print_bar(bar)
/// run_example(bar, count + 1)
/// }
/// False -> Nil
/// }
/// }
/// ```
pub fn print_bar(bar bar: ProgressStyle) {
let fill =
build_progress_fill(string_builder.new(), bar, bar.state.progress, 0)
|> string_builder.to_string

io.print_error(
codes.clear_line_code
<> codes.return_line_start_code
<> bar.left
<> fill
<> bar.right,
)
}

fn build_progress_fill(
fill: StringBuilder,
bar: ProgressStyle,
left_nonempty: Int,
count: Int,
) -> StringBuilder {
let fill = case left_nonempty > 0 {
True -> string_builder.append(fill, bar.fill.char)
False -> string_builder.append(fill, bar.empty.char)
}

case bar.length > count {
True -> build_progress_fill(fill, bar, left_nonempty - 1, count + 1)
False -> fill
}
}
7 changes: 7 additions & 0 deletions src/glitzer_ffi.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-module(glitzer_ffi).

% Public API
-export([sleep/1]).

sleep(Int) ->
timer:sleep(Int).
3 changes: 3 additions & 0 deletions src/glitzer_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function sleep(ms = 0) {
return new Promise(resolve => setTimeout(resolve, ms));
}