#!/usr/bin/env elixir # Export org-roam notes (per-file) to content/ via ox-hugo. # # Usage: # NOTES_DIR=~/notes elixir scripts/export.exs # elixir scripts/export.exs /path/to/notes # # The positional argument takes precedence over the NOTES_DIR env var. notes_dir = case System.argv() do [dir | _] -> dir [] -> System.get_env("NOTES_DIR") || (IO.puts(:stderr, "Usage: NOTES_DIR=/path/to/notes elixir scripts/export.exs"); System.halt(1)) end notes_dir = Path.expand(notes_dir) repo_root = __DIR__ |> Path.join("..") |> Path.expand() content_dir = Path.join(repo_root, "content") unless File.dir?(notes_dir) do IO.puts(:stderr, "Error: notes directory does not exist: #{notes_dir}") System.halt(1) end # Wipe content/, preserving .gitkeep IO.puts("==> Wiping #{content_dir}") content_dir |> File.ls!() |> Enum.reject(&(&1 == ".gitkeep")) |> Enum.each(fn entry -> Path.join(content_dir, entry) |> File.rm_rf!() end) # Collect all .org files IO.puts("==> Exporting org files from #{notes_dir}") org_files = Path.join(notes_dir, "**/*.org") |> Path.wildcard() if org_files == [] do IO.puts("No .org files found in #{notes_dir}") System.halt(0) end # Export each file via emacs --batch results = Enum.map(org_files, fn orgfile -> IO.puts(" exporting: #{orgfile}") # Mirror the notes subdirectory structure under content/ section = orgfile |> Path.dirname() |> Path.relative_to(notes_dir) {output, exit_code} = System.cmd( "emacs", [ "--batch", "--eval", "(require 'ox-hugo)", "--eval", ~s[(setq org-hugo-base-dir "#{repo_root}")], "--eval", ~s[(setq org-hugo-default-section-directory "#{section}")], "--visit", orgfile, "--funcall", "org-hugo-export-to-md" ], stderr_to_stdout: true ) # Filter noisy emacs startup lines, same as the shell script filtered = output |> String.split("\n") |> Enum.reject(&String.match?(&1, ~r/^Loading|^ad-handle|^For information/)) |> Enum.join("\n") if filtered != "", do: IO.puts(filtered) {orgfile, exit_code} end) failures = Enum.filter(results, fn {_, code} -> code != 0 end) if failures != [] do IO.puts(:stderr, "\nFailed to export #{length(failures)} file(s):") Enum.each(failures, fn {f, code} -> IO.puts(:stderr, " [exit #{code}] #{f}") end) System.halt(1) end md_count = Path.join(content_dir, "**/*.md") |> Path.wildcard() |> length() # Generate a default index.md if none was exported index_path = Path.join(content_dir, "index.md") unless File.exists?(index_path) do IO.puts("==> Generating default index.md") pages = Path.join(content_dir, "**/*.md") |> Path.wildcard() |> Enum.map(fn path -> slug = Path.relative_to(path, content_dir) |> Path.rootname() title = path |> File.read!() |> then(fn content -> case Regex.run(~r/^title\s*=\s*"(.+)"/m, content) do [_, t] -> t _ -> slug end end) {slug, title} end) |> Enum.sort_by(fn {_, title} -> title end) |> Enum.map(fn {slug, title} -> "- [#{title}](#{slug})" end) |> Enum.join("\n") File.write!(index_path, """ --- title: Index --- #{pages} """) end IO.puts("==> Done. #{md_count} markdown files in #{content_dir}")