Skip to main content

src/proute.gleam

import filepath
import gleam/io
import gleam/list
import gleam/result
import gleam/string
import proute/config
import proute/discover
import proute/exit
import proute/generate/page_input
import proute/generate/pages
import proute/generate/routes
import proute/validate/pages as page_validation
import simplifile

type GeneratedMount {
  GeneratedMount(files: List(GeneratedFile))
}

pub type GeneratedFile {
  GeneratedFile(path: String, contents: String)
}

pub type ProuteError {
  ConfigError(error: config.ConfigError)
  DiscoveryError(error: discover.DiscoverError)
  ValidationErrors(errors: List(page_validation.ValidationError))
  WriteError(path: String)
}

pub fn main() {
  case run() {
    Ok(files) ->
      files
      |> list.each(fn(file) {
        let GeneratedFile(path:, ..) = file
        io.println("Generated " <> path)
      })
    Error(error) -> {
      io.println_error(describe_error(error))
      exit.failure()
    }
  }
}

fn run() -> Result(List(GeneratedFile), ProuteError) {
  use config <- result.try(load() |> result.map_error(ConfigError))
  run_config(config)
}

pub fn run_config(
  config: config.Config,
) -> Result(List(GeneratedFile), ProuteError) {
  use generated <- result.try(generate_all(config))
  use files <- result.try(write_all(generated))

  Ok(files)
}

fn load() {
  case find_config_path() {
    Ok(path) ->
      case simplifile.read(path) {
        Ok(source) -> config.parse(source)
        Error(_) -> Error(config.MissingConfig(path))
      }
    Error(_) -> Error(config.MissingConfig("proute.toml"))
  }
}

fn generate_all(
  config: config.Config,
) -> Result(List(GeneratedFile), ProuteError) {
  use generated <- result.try(
    config.mounts
    |> list.try_map(generate_mount),
  )

  Ok(generated |> list.flat_map(fn(mount) { mount.files }))
}

fn generate_mount(mount: config.Mount) -> Result(GeneratedMount, ProuteError) {
  use mount_routes <- result.try(
    discover.discover_mount(mount)
    |> result.map_error(DiscoveryError),
  )
  use page_modules <- result.try(
    page_validation.validate_mount(mount_routes)
    |> result.map_error(ValidationErrors),
  )

  Ok(
    GeneratedMount(files: [
      GeneratedFile(
        path: mount.routes_output,
        contents: routes.generate(mount_routes),
      ),
      GeneratedFile(
        path: mount.page_input_output,
        contents: page_input.generate(mount_routes),
      ),
      GeneratedFile(
        path: mount.pages_output,
        contents: pages.generate(
          mount_routes,
          page_modules,
          mount.page_shared_state_type,
        ),
      ),
    ]),
  )
}

fn write_all(
  files: List(GeneratedFile),
) -> Result(List(GeneratedFile), ProuteError) {
  use _ <- result.try(
    files
    |> list.try_map(fn(file) {
      let GeneratedFile(path:, contents:) = file
      write_file(path, contents)
    }),
  )

  Ok(files)
}

fn write_file(path: String, contents: String) -> Result(Nil, ProuteError) {
  use _ <- result.try(
    simplifile.create_directory_all(filepath.directory_name(path))
    |> result.map_error(fn(_) { WriteError(path) }),
  )

  simplifile.write(to: path, contents: contents)
  |> result.map_error(fn(_) { WriteError(path) })
}

fn describe_error(error: ProuteError) -> String {
  case error {
    ConfigError(error) -> config.describe_error(error)
    DiscoveryError(error) -> discover.describe_error(error)
    ValidationErrors(errors) ->
      errors
      |> list.map(page_validation.describe_error)
      |> string.join("\n")
    WriteError(path) ->
      "Could not write generated file " <> string.inspect(path) <> "."
  }
}

fn find_config_path() -> Result(String, Nil) {
  case simplifile.current_directory() {
    Ok(directory) -> find_config_path_from(directory)
    Error(_) -> Error(Nil)
  }
}

fn find_config_path_from(directory: String) -> Result(String, Nil) {
  let path = directory <> "/proute.toml"

  case simplifile.is_file(path) {
    Ok(True) -> Ok(path)
    _ -> {
      let parent = filepath.directory_name(directory)

      case parent == directory {
        True -> Error(Nil)
        False -> find_config_path_from(parent)
      }
    }
  }
}