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:
publicis your contract. Onlypublicitems can be imported by other packages, and every one is something you'll have to keep working. Expose the minimum.- Make illegal states unrepresentable.
UInt8channels are 0–255 by construction — no validation, no way for a caller to pass300.
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:
/// @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 producesOptional[Color]and yields.None(viareturn 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 theOptionalor bails. Three single guards read flat and stop at the first bad channel.channelis internal: a top-levelfuncwithoutpublicis 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.