let configuration =
  object (self)
    method output_directory =
      env "OUTPUT_DIR" |> Option.value ~default:"_doc"
    method input_files =
      env "INPUT" |> Option.value ~default:""
      |> String.split ~on:(`Character ',')
      |> List.map ~f:(fun path ->
          try
            if Sys.is_directory path
            then all_files path
            else [path]
          with _ -> [])
      |> List.concat
    method index_file =
      env "INDEX" |> Option.value ~default:"README.md"
    method stylesheets =
      env "CSS" |> Option.map ~f:(String.split ~on:(`Character ','))
      |> Option.value ~default:default_stylesheets
    method api_doc_directory =
      env "API"
    method title_prefix =
      env "TITLE_PREFIX" |> Option.value ~default:""
    method title_substitutions =
      env "TITLE_SUBSTITUTIONS"
      |> Option.value_map ~default:[] ~f:parse_list_of_substitutions
    method title ?(with_prefix=true) t =
      let tt =
        List.find_map self#title_substitutions ~f:(function
          | (a, b) when
              a = t || (try Filename.chop_extension a = t with _ -> false->
            Some b
          | _ -> None)
        |> function
        | Some b -> b
        | None ->
          String.map t ~f:(function '_' -> ' ' | c -> c)
      in
      sprintf "%s%s" (if with_prefix then self#title_prefix else "") tt
    method command_substitutions =
      env "COMMAND_SUBSTITUTIONS"
      |> Option.value_map ~default:[] ~f:parse_list_of_substitutions
    method catch_module_paths =
      env "CATCH_MODULE_PATHS"
      |> Option.value_map ~default:[] ~f:parse_list_of_substitutions
      |> List.filter_map ~f:(fun (pattern, prefix) ->
          try Some (pattern, Re_posix.compile_pat pattern, prefix)
          with _ -> None)
    method add_to_menu =
      env "ADD_TO_MENU"
      |> Option.value ~default:""
    method display =
      let list_of_paths l =
        (List.map l ~f:(sprintf "  - %S") |> String.concat ~sep:"\n"in
      let variable_note var =
        say "  (%S is %s)" var
          (match env var with None -> "empty" | Some s -> sprintf "%S" s) in
      say "Output directory: %s" self#output_directory;
      variable_note "OUTPUT_DIR";
      say "Input files:\n%s"
        (list_of_paths self#input_files);
      variable_note "INPUT";
      say "Style sheets:\n%s" (list_of_paths self#stylesheets);
      variable_note "CSS";
      begin match self#api_doc_directory with
      | Some s -> say "Getting API docs from: %S" s
      | None -> say "No getting API docs (*Warning*)"
      end;
      variable_note "API";
      say "Title prefix: %S" self#title_prefix;
      variable_note "TITLE_PREFIX";
      say "Command substitutions:";
      List.iter self#command_substitutions (fun (a, b) -> say "  - %s → %s" a b);
      variable_note "COMMAND_SUBSTITUTIONS";
      say "Index file: %s" self#index_file;
      variable_note "INDEX";
      say "Catch module paths:";
      List.iter self#catch_module_paths (fun (a,_, b) ->
          say "  - %S → Prefix: %s" a b);
      variable_note "CATCH_MODULE_PATHS";
      say "Add to the menu: %S" self#add_to_menu;
      variable_note "ADD_TO_MENU";
      ()
  end