Skip to content

Commit

Permalink
feat: basic progress bar (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
miampf authored Jun 4, 2024
1 parent f27a8fc commit 2c038c1
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 2 deletions.
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));
}

0 comments on commit 2c038c1

Please sign in to comment.