Build the Library

By the end of this page you'll have a palette library that type-checks — a Color type you can build from RGB channels or a hex string, and format back to hex. It's about 40 lines of Kestrel, plus doc comments.

Step 1 — Scaffold a library

mkdir palette cd palette flock init

flock init creates a flock.toml and an empty src/ in the current directory. Open flock.toml and add an org — your publish namespace (your GitHub username works; you'll set it up properly on the next page):

[package] name = "palette" version = "0.1.0" org = "your-name" description = "Parse and format hex colors" [dependencies]

A library has no @main — it exposes types and functions for other packages to import. That also means you verify it with flock check, not flock build (which looks for an entry point).

Step 2 — The Color type

Create src/palette.ks with the type, a memberwise initializer, and doc comments:

module palette /// An RGB color with three 8-bit channels. /// /// `Color` stores its red, green, and blue components as `UInt8` values, so /// each channel is constrained to 0–255 by construction. Build one from raw /// channels with `init(red:green:blue:)`, or parse a CSS-style hex string with /// `init(hex:)`; `toHex` produces the hex form back. /// /// # Examples /// /// ``` /// let blue = Color(red: 58, green: 123, blue: 213); /// blue.toHex(); // "#3a7bd5" /// ``` public struct Color { public let red: UInt8 public let green: UInt8 public let blue: UInt8 /// @name From Channels /// Creates a color from its red, green, and blue channels. /// /// Each argument is a `UInt8` component in the range 0–255. /// /// # Examples /// /// ``` /// let orange = Color(red: 255, green: 128, blue: 0); /// ``` public init(red red: UInt8, green green: UInt8, blue blue: UInt8) { self.red = red; self.green = green; self.blue = blue; } }

Two things to notice — most of "library design" is just this:

  • public is your contract. Only public items can be imported by other packages, and every one is something you'll have to keep working. Expose the minimum.
  • Make illegal states unrepresentable. UInt8 channels are 0–255 by construction — no validation, no way for a caller to pass 300.

The third piece is the doc comments, which the registry renders on your package's page. The house style is a one-sentence summary, a short detail paragraph, an @name <Title> tag on each init (every initializer is named init, so the tag gives it a readable heading), and a fenced # Examples block.

Step 3 — Parse a hex string

A color library should accept "#3a7bd5". Add a failable initializer and a small internal helper:

<!-- sample: skip --> /// @name From Hex /// Parses a CSS-style hex color string into a `Color`. /// /// Accepts exactly six hex digits with an optional leading `#` — for example /// `"#3a7bd5"` or `"3a7bd5"` — and digits are case-insensitive. Returns /// `null` when the input isn't six valid hex digits. /// /// # Examples /// /// ``` /// match Color(hex: "#3a7bd5") { /// .Some(c) => c.red, // 58 /// .None => 0 /// } /// ``` public init(hex hex: String)? { let s = hex.asSlice(); let start = if hex.starts(with: "#") { 1 } else { 0 }; if hex.chars.count - start != 6 { return null } guard let some r = channel(s, start) else { return null } guard let some g = channel(s, start + 2) else { return null } guard let some b = channel(s, start + 4) else { return null } self.red = r; self.green = g; self.blue = b; }

And the helper, at the top level of the file:

// Parses the two characters starting at index `at` into a channel value (0–255). // Internal: without `public` it's visible only inside this package, never part // of the API, so it can be renamed or rewritten freely. func channel(s: StringSlice, at: Int64) -> Optional[UInt8] { UInt8(parsing: s.subslice(from: at, to: at + 2).toOwned(), radix: 16) }
  • init(hex hex: String)? is a failable initializer — it produces Optional[Color] and yields .None (via return null) on bad input. Note the failure in the doc comment so callers know to handle it.
  • UInt8(parsing:, radix:) is the standard library's number parser — give it two hex characters and base 16; no digit-by-digit math of your own.
  • guard let some r = … else { … } unwraps the Optional or bails. Three single guards read flat and stop at the first bad channel.
  • channel is internal: a top-level func without public is visible only inside your package, so it never becomes part of your API — you can rename or rewrite it freely. It uses a plain // comment, not ///, since doc comments are for the public surface.

Step 4 — Format back to hex

<!-- sample: skip --> /// Formats the color as a lowercase `"#rrggbb"` hex string. /// /// Each channel becomes two zero-padded hex digits, so the result is always /// a `#` followed by six digits. /// /// # Examples /// /// ``` /// Color(red: 10, green: 0, blue: 5).toHex(); // "#0a0005" /// ``` public func toHex() -> String { "#\(self.red:02x)\(self.green:02x)\(self.blue:02x)" }

\(value:02x) is an interpolation format spec: x is lowercase hex, and 02 zero-pads to two digits — so a channel of 10 formats as 0a, giving a correct six-digit string every time.

Step 5 — Check it

flock check

You should see:

Checking palette... Check passed

The whole library

module palette /// An RGB color with three 8-bit channels. /// /// `Color` stores its red, green, and blue components as `UInt8` values, so /// each channel is constrained to 0–255 by construction. Build one from raw /// channels with `init(red:green:blue:)`, or parse a CSS-style hex string with /// `init(hex:)`; `toHex` produces the hex form back. /// /// # Examples /// /// ``` /// let blue = Color(red: 58, green: 123, blue: 213); /// blue.toHex(); // "#3a7bd5" /// ``` public struct Color { public let red: UInt8 public let green: UInt8 public let blue: UInt8 /// @name From Channels /// Creates a color from its red, green, and blue channels. /// /// Each argument is a `UInt8` component in the range 0–255. /// /// # Examples /// /// ``` /// let orange = Color(red: 255, green: 128, blue: 0); /// ``` public init(red red: UInt8, green green: UInt8, blue blue: UInt8) { self.red = red; self.green = green; self.blue = blue; } /// @name From Hex /// Parses a CSS-style hex color string into a `Color`. /// /// Accepts exactly six hex digits with an optional leading `#` — for example /// `"#3a7bd5"` or `"3a7bd5"` — and digits are case-insensitive. Returns /// `null` when the input isn't six valid hex digits. /// /// # Examples /// /// ``` /// match Color(hex: "#3a7bd5") { /// .Some(c) => c.red, // 58 /// .None => 0 /// } /// ``` public init(hex hex: String)? { let s = hex.asSlice(); let start = if hex.starts(with: "#") { 1 } else { 0 }; if hex.chars.count - start != 6 { return null } guard let some r = channel(s, start) else { return null } guard let some g = channel(s, start + 2) else { return null } guard let some b = channel(s, start + 4) else { return null } self.red = r; self.green = g; self.blue = b; } /// Formats the color as a lowercase `"#rrggbb"` hex string. /// /// Each channel becomes two zero-padded hex digits, so the result is always /// a `#` followed by six digits. /// /// # Examples /// /// ``` /// Color(red: 10, green: 0, blue: 5).toHex(); // "#0a0005" /// ``` public func toHex() -> String { "#\(self.red:02x)\(self.green:02x)\(self.blue:02x)" } } // Parses the two characters starting at index `at` into a channel value (0–255). // Internal: without `public` it's visible only inside this package, never part // of the API, so it can be renamed or rewritten freely. func channel(s: StringSlice, at: Int64) -> Optional[UInt8] { UInt8(parsing: s.subslice(from: at, to: at + 2).toOwned(), radix: 16) }

That's a real, publishable library: a focused public API, documented to the house style, with the internals kept private. Next: Publish Your Package — sign in, get a token, set your org, and flock publish.