Skip to content

Commit

Permalink
feat(graindoc): Allow directory input & output (grain-lang#1263)
Browse files Browse the repository at this point in the history
chore(docs): Generate or update markdown for entire stdlib
  • Loading branch information
phated authored May 27, 2022
1 parent 4637667 commit d4cb8ab
Show file tree
Hide file tree
Showing 35 changed files with 4,016 additions and 151 deletions.
127 changes: 102 additions & 25 deletions compiler/graindoc/graindoc.re
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ open Compile;
open Grain_typed;
open Grain_utils;
open Grain_diagnostics;
open Filename;
open Grain_utils.Filepath.Args;

let () =
Printexc.register_printer(exc =>
Expand All @@ -28,13 +28,17 @@ let () =
);

[@deriving cmdliner]
type params = {
type io_params = {
/** Grain source file for which to extract documentation */
[@pos 0] [@docv "FILE"]
input: Filepath.Args.ExistingFile.t,
input: ExistingFileOrDirectory.t,
/** Output filename */
[@name "o"] [@docv "FILE"]
output: option(Filepath.Args.MaybeExistingFile.t),
output: option(MaybeExistingFileOrDirectory.t),
};

[@deriving cmdliner]
type params = {
/**
The version to use as current when generating markdown for `@since` and `@history` attributes.
Any future versions will be replace with `next` in the output.
Expand All @@ -43,10 +47,10 @@ type params = {
current_version: option(string),
};

let compile_typed = (opts: params) => {
let input = Filepath.to_string(opts.input);

switch (Compile.compile_file(~hook=stop_after_typed, input)) {
let compile_typed = (input: Fp.t(Fp.absolute)) => {
switch (
Compile.compile_file(~hook=stop_after_typed, Filepath.to_string(input))
) {
| exception exn =>
let bt =
if (Printexc.backtrace_status()) {
Expand All @@ -71,7 +75,7 @@ let compile_typed = (opts: params) => {
};

let generate_docs =
({current_version, output}: params, program: Typedtree.typed_program) => {
(~current_version, ~output=?, program: Typedtree.typed_program) => {
let comments = Comments.to_ordered(program.comments);

let env = program.env;
Expand Down Expand Up @@ -205,15 +209,12 @@ let generate_docs =

let contents = Buffer.to_bytes(buf);
switch (output) {
| Some(NotExists(outfile)) =>
Fs_access.ensure_parent_directory_exists(Filepath.to_string(outfile))
| _ => ()
};

switch (output) {
| Some(Exists(outfile))
| Some(NotExists(outfile)) =>
let oc = Fs_access.open_file_for_writing(Filepath.to_string(outfile));
| Some(outfile) =>
let outfile = Filepath.to_string(outfile);
// TODO: This crashes if you do something weird like `-o stdout/map.gr/foo`
// because `foo` doesn't exist so it tries to mkdir it and raises
Fs_access.ensure_parent_directory_exists(outfile);
let oc = Fs_access.open_file_for_writing(outfile);
output_bytes(oc, contents);
close_out(oc);
| None => print_bytes(contents)
Expand All @@ -222,13 +223,87 @@ let generate_docs =
();
};

let graindoc = opts => {
let program = compile_typed(opts);
try(generate_docs(opts, program)) {
| exn =>
Format.eprintf("@[%s@]@.", Printexc.to_string(exn));
exit(2);
type run = {
input_path: Fp.t(Fp.absolute),
output_path: option(Fp.t(Fp.absolute)),
};

let enumerate_directory = (input_dir_path, output_dir_path) => {
let all_files = Array.to_list(Fs_access.readdir(input_dir_path));
let grain_files =
List.filter(
filepath => Filename.extension(Fp.toString(filepath)) == ".gr",
all_files,
);
List.map(
filepath => {
// We relativize between the input directory and the full filepath
// such that we can reconstruct the directory structure of the input directory
let relative_path =
Fp.relativizeExn(~source=input_dir_path, ~dest=filepath);
let gr_basename = Option.get(Fp.baseName(relative_path));
let md_basename =
Filepath.String.remove_extension(gr_basename) ++ ".md";
let dirname = Fp.dirName(relative_path);
let md_relative_path = Fp.join(dirname, Fp.relativeExn(md_basename));
let output_path = Fp.join(output_dir_path, md_relative_path);
{input_path: filepath, output_path: Some(output_path)};
},
grain_files,
);
};

let enumerate_runs = opts =>
switch (opts.input, opts.output) {
| (File(input_file_path), None) =>
`Ok([{input_path: input_file_path, output_path: None}])
| (File(input_file_path), Some(Exists(File(output_file_path)))) =>
`Ok([
{input_path: input_file_path, output_path: Some(output_file_path)},
])
| (File(input_file_path), Some(NotExists(output_file_path))) =>
`Ok([
{input_path: input_file_path, output_path: Some(output_file_path)},
])
| (Directory(_), None) =>
`Error((
false,
"Directory input must be used with `-o` flag to specify output directory",
))
| (Directory(input_dir_path), Some(Exists(Directory(output_dir_path)))) =>
`Ok(enumerate_directory(input_dir_path, output_dir_path))
| (Directory(input_dir_path), Some(NotExists(output_dir_path))) =>
`Ok(enumerate_directory(input_dir_path, output_dir_path))
| (File(input_file_path), Some(Exists(Directory(output_dir_path)))) =>
`Error((
false,
"Using a file as input cannot be combined with directory output",
))
| (Directory(_), Some(Exists(File(_)))) =>
`Error((
false,
"Using a directory as input cannot be written as a single file output",
))
};

let graindoc = (opts, runs) => {
List.iter(
({input_path, output_path}) => {
let program = compile_typed(input_path);
try(
generate_docs(
~current_version=opts.current_version,
~output=?output_path,
program,
)
) {
| exn =>
Format.eprintf("@[%s@]@.", Printexc.to_string(exn));
exit(2);
};
},
runs,
);
};

let cmd = {
Expand All @@ -243,7 +318,9 @@ let cmd = {

Cmd.v(
Cmd.info(Sys.argv[0], ~version, ~doc),
Grain_utils.Config.with_cli_options(graindoc) $ params_cmdliner_term(),
Grain_utils.Config.with_cli_options(graindoc)
$ params_cmdliner_term()
$ ret(const(enumerate_runs) $ io_params_cmdliner_term()),
);
};

Expand Down
93 changes: 91 additions & 2 deletions compiler/src/utils/filepath.re
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,64 @@ module Args = {
let cmdliner_converter = (prsr, prntr);
};

module ExistingFileOrDirectory = {
type t =
| File(Fp.t(Fp.absolute))
| Directory(Fp.t(Fp.absolute));

type err =
| InvalidPath(string)
| NotExists(Fp.t(Fp.absolute))
| InvalidFileType(Fp.t(Fp.absolute));

let query = fname => {
switch (from_string(fname)) {
| Some(path) =>
let abs_path = derelativize(path);
switch (Fs.query(abs_path)) {
| Some(File(path, _stat)) => Ok(File(path))
| Some(Dir(path, _)) => Ok(Directory(path))
| Some(Other(path, _, _)) => Error(InvalidFileType(path))
| Some(Link(_, realpath, _)) =>
Error(InvalidFileType(derelativize(realpath)))
| None => Error(NotExists(abs_path))
};
| None => Error(InvalidPath(fname))
};
};

let prsr = fname => {
switch (query(fname)) {
| Ok(file) => `Ok(file)
| Error(InvalidFileType(path)) =>
`Error(
Format.sprintf(
"%s exists but is not a file or directory",
to_string(path),
),
)
| Error(NotExists(path)) =>
`Error(Format.sprintf("%s does not exist", to_string(path)))
| Error(InvalidPath(fname)) =>
`Error(Format.sprintf("Invalid path: %s", fname))
};
};

let prntr = (formatter, value) => {
switch (value) {
| File(path) => Format.fprintf(formatter, "File: %s", to_string(path))
| Directory(path) =>
Format.fprintf(formatter, "Directory: %s", to_string(path))
};
};

let cmdliner_converter = (prsr, prntr);
};

module MaybeExistingFile = {
type t =
| Exists(Fp.t(Fp.absolute))
| NotExists(Fp.t(Fp.absolute));
| Exists(ExistingFile.t)
| NotExists(ExistingFile.t);

let prsr = fname => {
switch (ExistingFile.query(fname)) {
Expand All @@ -213,4 +267,39 @@ module Args = {

let cmdliner_converter = (prsr, prntr);
};

module MaybeExistingFileOrDirectory = {
type t =
| Exists(ExistingFileOrDirectory.t)
| NotExists(Fp.t(Fp.absolute));

let prsr = fname => {
switch (ExistingFileOrDirectory.query(fname)) {
| Ok(path) => `Ok(Exists(path))
| Error(NotExists(path)) => `Ok(NotExists(path))
| Error(InvalidFileType(path)) =>
`Error(
Format.sprintf(
"%s exists but is not a file or directory",
to_string(path),
),
)
| Error(InvalidPath(fname)) =>
`Error(Format.sprintf("Invalid path: %s", fname))
};
};

let prntr = (formatter, value) => {
switch (value) {
| Exists(File(path)) =>
Format.fprintf(formatter, "File: %s", to_string(path))
| Exists(Directory(path)) =>
Format.fprintf(formatter, "Directory: %s", to_string(path))
| NotExists(path) =>
Format.fprintf(formatter, "Path: %s", to_string(path))
};
};

let cmdliner_converter = (prsr, prntr);
};
};
Loading

0 comments on commit d4cb8ab

Please sign in to comment.