harn-stdlib 0.8.168

Embedded Harn standard library source catalog
Documentation
/**
 * std/slug — memorable, non-secret names for runs, agents, incidents, and fixtures.
 *
 * Import with: import "std/slug"
 *
 * @effects: ["random"]
 * @errors: []
 */
const __SLUG_ADJECTIVES = [
  "able",
  "bright",
  "calm",
  "careful",
  "clever",
  "crisp",
  "curious",
  "dapper",
  "direct",
  "eager",
  "fair",
  "fresh",
  "gentle",
  "honest",
  "kind",
  "lively",
  "lucid",
  "merry",
  "nimble",
  "patient",
  "plain",
  "precise",
  "quiet",
  "ready",
  "robust",
  "steady",
  "tidy",
  "useful",
  "vivid",
  "warm",
  "witty",
  "zesty",
]

const __SLUG_NOUNS = [
  "anchor",
  "apricot",
  "atlas",
  "battery",
  "beacon",
  "bridge",
  "button",
  "canvas",
  "celery",
  "circuit",
  "compass",
  "corpus",
  "delta",
  "ember",
  "fiber",
  "garden",
  "harbor",
  "index",
  "kernel",
  "lantern",
  "ledger",
  "matrix",
  "meadow",
  "notebook",
  "orbit",
  "parcel",
  "pixel",
  "prism",
  "quartz",
  "ribbon",
  "socket",
  "soup",
  "staple",
  "summit",
  "thread",
  "vector",
]

const __SLUG_VERBS = [
  "align",
  "build",
  "chart",
  "check",
  "craft",
  "debug",
  "draft",
  "gather",
  "index",
  "inspect",
  "merge",
  "patch",
  "polish",
  "reason",
  "render",
  "route",
  "shape",
  "sketch",
  "sort",
  "tune",
]

/**
 * Return the default adjective dictionary used by `random_slug` and `slug_from`.
 *
 * @effects: []
 * @errors: []
 */
pub fn slug_adjectives() {
  return __SLUG_ADJECTIVES
}

/**
 * Return the default noun dictionary used by `random_slug` and `slug_from`.
 *
 * @effects: []
 * @errors: []
 */
pub fn slug_nouns() {
  return __SLUG_NOUNS
}

/**
 * Return the default verb dictionary used by explicit slug patterns.
 *
 * @effects: []
 * @errors: []
 */
pub fn slug_verbs() {
  return __SLUG_VERBS
}

/**
 * Return the default `{adjectives, nouns, verbs}` slug dictionary.
 *
 * @effects: []
 * @errors: []
 */
pub fn slug_dictionary() {
  return {adjectives: __SLUG_ADJECTIVES, nouns: __SLUG_NOUNS, verbs: __SLUG_VERBS}
}

/**
 * Generate a non-secret human-readable slug from the default or supplied dictionary.
 *
 * Options: `segments`, `separator`, `pattern`, `prefix`, `suffix`, `rng`, and
 * `dictionary`.
 *
 * @effects: ["random"]
 * @errors: []
 */
pub fn random_slug(options = nil) {
  let opts = __slug_options(options)
  var words = []
  var index = 0
  while index < opts.segments {
    let kind = __slug_segment_kind(opts.pattern, index, opts.segments)
    let bucket = __slug_bucket(kind, opts.dictionary)
    words = words + [__slug_random_word(bucket, opts.rng)]
    index = index + 1
  }
  return __slug_join_with_affixes(words, opts)
}

/**
 * Generate a deterministic human-readable slug for `value`.
 *
 * Options match `random_slug`; `salt` can partition deterministic namespaces.
 *
 * @effects: []
 * @errors: []
 */
pub fn slug_from(value, options = nil) {
  let opts = __slug_options(options)
  let seed = to_string(opts.salt ?? "") + ":" + to_string(value)
  var words = []
  var index = 0
  while index < opts.segments {
    let kind = __slug_segment_kind(opts.pattern, index, opts.segments)
    let bucket = __slug_bucket(kind, opts.dictionary)
    words = words + [bucket[__slug_seed_index(seed, index, len(bucket))]]
    index = index + 1
  }
  return __slug_join_with_affixes(words, opts)
}

/**
 * Alias for `slug_from`.
 *
 * @effects: []
 * @errors: []
 */
pub fn deterministic_slug(value, options = nil) {
  return slug_from(value, options)
}

/**
 * Generate a random slug when `value` is nil, otherwise a deterministic slug.
 *
 * @effects: ["random"]
 * @errors: []
 */
pub fn slug(value = nil, options = nil) {
  if value == nil {
    return random_slug(options)
  }
  return slug_from(value, options)
}

/**
 * Convert arbitrary text into a lowercase separator-delimited slug.
 *
 * @effects: []
 * @errors: []
 */
pub fn slugify(value, options = nil) {
  let separator = __slug_separator(options?.separator ?? "-")
  return __slugify_text(value, separator)
}

fn __slug_options(options) {
  let raw = options ?? {}
  var segments = to_int(raw?.segments ?? 2)
  if segments < 1 {
    segments = 1
  }
  if segments > 8 {
    segments = 8
  }
  let separator = __slug_separator(raw?.separator ?? "-")
  let custom_dictionary = raw?.dictionary ?? {}
  let prefix = __slug_affix(raw?.prefix, separator)
  let suffix = __slug_affix(raw?.suffix, separator)
  var pattern = raw?.pattern ?? []
  if type_of(pattern) != "list" {
    pattern = []
  }
  return {
    segments: segments,
    separator: separator,
    pattern: pattern,
    prefix: prefix,
    suffix: suffix,
    salt: raw?.salt ?? "",
    rng: raw?.rng,
    dictionary: {
      adjectives: __slug_nonempty_list(custom_dictionary?.adjectives, __SLUG_ADJECTIVES),
      nouns: __slug_nonempty_list(custom_dictionary?.nouns, __SLUG_NOUNS),
      verbs: __slug_nonempty_list(custom_dictionary?.verbs, __SLUG_VERBS),
    },
  }
}

fn __slug_separator(value) {
  let separator = to_string(value ?? "-")
  if separator == "" {
    return "-"
  }
  return separator
}

fn __slug_nonempty_list(value, fallback) {
  if type_of(value) == "list" && len(value) > 0 {
    return value
  }
  return fallback
}

fn __slug_segment_kind(pattern, index, segments) {
  if len(pattern) > 0 {
    return pattern[index % len(pattern)]
  }
  if segments == 1 {
    return "noun"
  }
  if index == 0 {
    return "adjective"
  }
  return "noun"
}

fn __slug_bucket(kind, dictionary) {
  let normalized = lowercase(to_string(kind ?? "noun"))
  if normalized == "adjective" || normalized == "adjectives" || normalized == "adj" {
    return dictionary.adjectives
  }
  if normalized == "verb" || normalized == "verbs" {
    return dictionary.verbs
  }
  return dictionary.nouns
}

fn __slug_random_word(bucket, rng) {
  var word = nil
  if rng != nil {
    word = random_choice(rng, bucket)
  } else {
    word = harness.random.choice(bucket)
  }
  if word == nil && len(bucket) > 0 {
    return bucket[0]
  }
  return word
}

fn __slug_seed_index(seed, index, size) {
  if size <= 0 {
    return 0
  }
  let digest = sha256(seed + ":" + to_string(index))
  var value = 0
  var offset = 0
  while offset < 8 {
    value = value * 16 + __slug_hex_digit(substring(digest, offset, offset + 1))
    offset = offset + 1
  }
  return value % size
}

fn __slug_hex_digit(ch) {
  let idx = "0123456789abcdef".index_of(lowercase(ch ?? ""))
  if idx < 0 {
    return 0
  }
  return idx
}

fn __slug_join_with_affixes(words, opts) {
  var parts = []
  if opts.prefix != nil && opts.prefix != "" {
    parts = parts + [opts.prefix]
  }
  parts = parts + words
  if opts.suffix != nil && opts.suffix != "" {
    parts = parts + [opts.suffix]
  }
  return join(parts, opts.separator)
}

fn __slug_affix(value, separator) {
  if value == nil {
    return nil
  }
  return __slugify_text(value, separator)
}

fn __slugify_text(value, separator) {
  let text = lowercase(trim(to_string(value ?? "")))
  var out = regex_replace("[^a-z0-9]+", "-", text)
  out = regex_replace("^-+|-+$", "", out)
  if separator != "-" {
    out = replace(out, "-", separator)
  }
  return out
}