Merge pull request #1347 from jcollie/generate-manpages

Generate manpages (and other documentation) using help strings.
This commit is contained in:
Mitchell Hashimoto
2024-01-21 14:58:03 -08:00
committed by GitHub
19 changed files with 880 additions and 494 deletions

View File

@ -201,7 +201,7 @@ jobs:
run: nix develop -c zig build -Dapp-runtime=none test run: nix develop -c zig build -Dapp-runtime=none test
- name: Test GTK Build - name: Test GTK Build
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-libadwaita=true run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-libadwaita=true -Demit-docs
- name: Test GLFW Build - name: Test GLFW Build
run: nix develop -c zig build -Dapp-runtime=glfw run: nix develop -c zig build -Dapp-runtime=glfw

View File

@ -15,6 +15,7 @@ const LibtoolStep = @import("src/build/LibtoolStep.zig");
const LipoStep = @import("src/build/LipoStep.zig"); const LipoStep = @import("src/build/LipoStep.zig");
const XCFrameworkStep = @import("src/build/XCFrameworkStep.zig"); const XCFrameworkStep = @import("src/build/XCFrameworkStep.zig");
const Version = @import("src/build/Version.zig"); const Version = @import("src/build/Version.zig");
const Command = @import("src/Command.zig");
// Do a comptime Zig version requirement. The required Zig version is // Do a comptime Zig version requirement. The required Zig version is
// somewhat arbitrary: it is meant to be a version that we feel works well, // somewhat arbitrary: it is meant to be a version that we feel works well,
@ -102,6 +103,18 @@ pub fn build(b: *std.Build) !void {
"Name of the conformance app to run with 'run' option.", "Name of the conformance app to run with 'run' option.",
); );
const emit_docs = b.option(
bool,
"emit-docs",
"Build and install auto-generated documentation (requires pandoc)",
) orelse emit_docs: {
// We only default to true if we can find pandoc.
const path = Command.expandPath(b.allocator, "pandoc") catch
break :emit_docs false;
defer if (path) |p| b.allocator.free(p);
break :emit_docs path != null;
};
const emit_test_exe = b.option( const emit_test_exe = b.option(
bool, bool,
"emit-test-exe", "emit-test-exe",
@ -414,6 +427,9 @@ pub fn build(b: *std.Build) !void {
}); });
} }
// Documenation
if (emit_docs) buildDocumentation(b, version);
// App (Linux) // App (Linux)
if (target.result.os.tag == .linux and config.app_runtime != .none) { if (target.result.os.tag == .linux and config.app_runtime != .none) {
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html // https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html
@ -1148,6 +1164,73 @@ fn addHelp(
} }
} }
/// Generate documentation (manpages, etc.) from help strings
fn buildDocumentation(
b: *std.Build,
version: std.SemanticVersion,
) void {
const manpages = [_]struct {
name: []const u8,
section: []const u8,
}{
.{ .name = "ghostty", .section = "1" },
.{ .name = "ghostty", .section = "5" },
};
inline for (manpages) |manpage| {
const generate_markdown = b.addExecutable(.{
.name = "mdgen_" ++ manpage.name ++ "_" ++ manpage.section,
.root_source_file = .{
.path = "src/mdgen_" ++ manpage.name ++ "_" ++ manpage.section ++ ".zig",
},
.target = b.host,
});
addHelp(b, generate_markdown);
const generate_markdown_options = b.addOptions();
generate_markdown_options.addOption(std.SemanticVersion, "version", version);
generate_markdown.root_module.addOptions("build_options", generate_markdown_options);
const generate_markdown_step = b.addRunArtifact(generate_markdown);
const markdown_output = generate_markdown_step.captureStdOut();
b.getInstallStep().dependOn(&b.addInstallFile(
markdown_output,
"share/ghostty/doc/" ++ manpage.name ++ "." ++ manpage.section ++ ".md",
).step);
const generate_html = b.addSystemCommand(&.{"pandoc"});
generate_html.addArgs(&.{
"--standalone",
"--from",
"markdown",
"--to",
"html",
});
generate_html.addFileArg(markdown_output);
b.getInstallStep().dependOn(&b.addInstallFile(
generate_html.captureStdOut(),
"share/ghostty/doc/" ++ manpage.name ++ "." ++ manpage.section ++ ".html",
).step);
const generate_manpage = b.addSystemCommand(&.{"pandoc"});
generate_manpage.addArgs(&.{
"--standalone",
"--from",
"markdown",
"--to",
"man",
});
generate_manpage.addFileArg(markdown_output);
b.getInstallStep().dependOn(&b.addInstallFile(
generate_manpage.captureStdOut(),
"share/man/man" ++ manpage.section ++ "/" ++ manpage.name ++ "." ++ manpage.section,
).step);
}
}
fn benchSteps( fn benchSteps(
b: *std.Build, b: *std.Build,
target: std.Build.ResolvedTarget, target: std.Build.ResolvedTarget,

View File

@ -45,6 +45,7 @@
pixman, pixman,
zlib, zlib,
alejandra, alejandra,
pandoc,
}: let }: let
# See package.nix. Keep in sync. # See package.nix. Keep in sync.
rpathLibs = rpathLibs =
@ -80,6 +81,7 @@ in
# For builds # For builds
llvmPackages_latest.llvm llvmPackages_latest.llvm
ncurses ncurses
pandoc
pkg-config pkg-config
scdoc scdoc
zig zig

View File

@ -23,6 +23,7 @@
ncurses, ncurses,
pkg-config, pkg-config,
zig_0_12, zig_0_12,
pandoc,
revision ? "dirty", revision ? "dirty",
}: let }: let
# The Zig hook has no way to select the release type without actual # The Zig hook has no way to select the release type without actual
@ -90,6 +91,7 @@ in
nativeBuildInputs = [ nativeBuildInputs = [
git git
ncurses ncurses
pandoc
pkg-config pkg-config
zig012Hook zig012Hook
wrapGAppsHook4 wrapGAppsHook4

View File

@ -0,0 +1,40 @@
# FILES
_\$XDG_CONFIG_HOME/ghostty/config_
: Location of the default configuration file.
_\$LOCALAPPDATA/ghostty/config_
: **On Windows**, if _\$XDG_CONFIG_HOME_ is not set, _\$LOCALAPPDATA_ will be searched
for configuration files.
# ENVIRONMENT
**TERM**
: Defaults to `xterm-ghostty`. Can be configured with the `term` configuration option.
**GHOSTTY_RESOURCES_DIR**
: Where the Ghostty resources can be found.
**XDG_CONFIG_HOME**
: Default location for configuration files.
**LOCALAPPDATA**
: **WINDOWS ONLY:** alternate location to search for configuration files.
# BUGS
See GitHub issues: <https://github.com/mitchellh/ghostty/issues>
# AUTHOR
Mitchell Hashimoto <m@mitchellh.com>
# SEE ALSO
**ghostty(5)**

View File

@ -0,0 +1,23 @@
% GHOSTTY(1) Version @@VERSION@@ | Ghostty terminal emulator
# NAME
**ghostty** - Ghostty terminal emulator
# DESCRIPTION
Ghostty is a cross-platform, GPU-accelerated terminal emulator that aims to push
the boundaries of what is possible with a terminal emulator by exposing modern,
opt-in features that enable CLI tool developers to build more feature rich,
interactive applications.
There are a number of excellent terminal emulator options that exist today.
The unique goal of Ghostty is to have a platform for experimenting with modern,
optional, non-standards-compliant features to enhance the capabilities of CLI
applications. We aim to be the best in this category, and competitive in the
rest.
While aiming for this ambitious goal, Ghostty is a fully standards compliant
terminal emulator that aims to remain compatible with all existing shells and
software. You can use this as a drop-in replacement for your existing terminal
emulator.

View File

@ -0,0 +1,32 @@
# FILES
_\$XDG_CONFIG_HOME/ghostty/config_
: Location of the default configuration file.
_\$LOCALAPPDATA/ghostty/config_
: **On Windows**, if _\$XDG_CONFIG_HOME_ is not set, _\$LOCALAPPDATA_ will be searched
for configuration files.
# ENVIRONMENT
**XDG_CONFIG_HOME**
: Default location for configuration files.
**LOCALAPPDATA**
: **WINDOWS ONLY:** alternate location to search for configuration files.
# BUGS
See GitHub issues: <https://github.com/mitchellh/ghostty/issues>
# AUTHOR
Mitchell Hashimoto <m@mitchellh.com>
# SEE ALSO
**ghostty(1)**

View File

@ -0,0 +1,90 @@
% GHOSTTY(5) Version @@VERSION@@ | Ghostty terminal emulator configuration file
# NAME
**ghostty** - Ghostty terminal emulator configuration file
# DESCRIPTION
To configure Ghostty, you must use a configuration file. GUI-based configuration
is on the roadmap but not yet supported. The configuration file must be placed
at `$XDG_CONFIG_HOME/ghostty/config`, which defaults to `~/.config/ghostty/config`
if the [XDG environment is not set](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
The file format is documented below as an example:
# The syntax is "key = value". The whitespace around the equals doesn't matter.
background = 282c34
foreground= ffffff
# Blank lines are ignored!
keybind = ctrl+z=close_surface
keybind = ctrl+d=new_split:right
# Colors can be changed by setting the 16 colors of `palette`, which each color
# being defined as regular and bold.
#
# black
palette = 0=#1d2021
palette = 8=#7c6f64
# red
palette = 1=#cc241d
palette = 9=#fb4934
# green
palette = 2=#98971a
palette = 10=#b8bb26
# yellow
palette = 3=#d79921
palette = 11=#fabd2f
# blue
palette = 4=#458588
palette = 12=#83a598
# purple
palette = 5=#b16286
palette = 13=#d3869b
# aqua
palette = 6=#689d6a
palette = 14=#8ec07c
# white
palette = 7=#a89984
palette = 15=#fbf1c7
You can view all available configuration options and their documentation by
executing the command `ghostty +show-config --default --docs`. Note that this will
output the full default configuration with docs to stdout, so you may want to
pipe that through a pager, an editor, etc.
Note: You'll see a lot of weird blank configurations like `font-family =`. This
is a valid syntax to specify the default behavior (no value). The `+show-config`
outputs it so its clear that key is defaulting and also to have something to
attach the doc comment to.
You can also see and read all available configuration options in the source
Config structure. The available keys are the keys verbatim, and their possible
values are typically documented in the comments. You also can search for
the public config files of many Ghostty users for examples and inspiration.
## Configuration Errors
If your configuration file has any errors, Ghostty does its best to ignore
them and move on. Configuration errors currently show up in the log. The log
is written directly to stderr, so it is up to you to figure out how to access
that for your system (for now). On macOS, you can also use the system `log` CLI
utility. See the Mac App section for more information.
## Debugging Configuration
You can verify that configuration is being properly loaded by looking at the
debug output of Ghostty. Documentation for how to view the debug output is in
the "building Ghostty" section at the end of the README.
In the debug output, you should see in the first 20 lines or so messages about
loading (or not loading) a configuration file, as well as any errors it may have
encountered. Configuration errors are also shown in a dedicated window on both
macOS and Linux (GTK). Ghostty does not treat configuration errors as fatal and
will fall back to default values for erroneous keys.
You can also view the full configuration Ghostty is loading using `ghostty
+show-config` from the command-line. Use the `--help` flag to additional options
for that command.

85
src/build/mdgen/mdgen.zig Normal file
View File

@ -0,0 +1,85 @@
const std = @import("std");
const help_strings = @import("help_strings");
const build_options = @import("build_options");
const Config = @import("../../config/Config.zig");
const Action = @import("../../cli/action.zig").Action;
pub fn substitute(alloc: std.mem.Allocator, input: []const u8, writer: anytype) !void {
const version_string = try std.fmt.allocPrint(alloc, "{}", .{build_options.version});
defer alloc.free(version_string);
const output = try alloc.alloc(u8, std.mem.replacementSize(
u8,
input,
"@@VERSION@@",
version_string,
));
defer alloc.free(output);
_ = std.mem.replace(u8, input, "@@VERSION@@", version_string, output);
try writer.writeAll(output);
}
pub fn genConfig(writer: anytype, cli: bool) !void {
try writer.writeAll(
\\
\\# CONFIGURATION OPTIONS
\\
\\
);
inline for (@typeInfo(Config).Struct.fields) |field| {
if (field.name[0] == '_') continue;
try writer.writeAll("`");
if (cli) try writer.writeAll("--");
try writer.writeAll(field.name);
try writer.writeAll("`\n\n");
if (@hasDecl(help_strings.Config, field.name)) {
var iter = std.mem.splitScalar(u8, @field(help_strings.Config, field.name), '\n');
var first = true;
while (iter.next()) |s| {
try writer.writeAll(if (first) ": " else " ");
try writer.writeAll(s);
try writer.writeAll("\n");
first = false;
}
try writer.writeAll("\n\n");
}
}
}
pub fn genActions(writer: anytype) !void {
try writer.writeAll(
\\
\\# COMMAND LINE ACTIONS
\\
\\
);
inline for (@typeInfo(Action).Enum.fields) |field| {
const action = std.meta.stringToEnum(Action, field.name).?;
switch (action) {
.help => try writer.writeAll("`--help`\n\n"),
.version => try writer.writeAll("`--version`\n\n"),
else => {
try writer.writeAll("`+");
try writer.writeAll(field.name);
try writer.writeAll("`\n\n");
},
}
if (@hasDecl(help_strings.Action, field.name)) {
var iter = std.mem.splitScalar(u8, @field(help_strings.Action, field.name), '\n');
var first = true;
while (iter.next()) |s| {
try writer.writeAll(if (first) ": " else " ");
try writer.writeAll(s);
try writer.writeAll("\n");
first = false;
}
try writer.writeAll("\n\n");
}
}
}

View File

@ -3,9 +3,9 @@ const Allocator = std.mem.Allocator;
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("action.zig").Action;
// Note that this options struct doesn't implement the `help` decl like // Note that this options struct doesn't implement the `help` decl like other
// other actions. That is because the help command is special and wants to // actions. That is because the help command is special and wants to handle its
// handle its own logic around help detection. // own logic around help detection.
pub const Options = struct { pub const Options = struct {
/// This must be registered so that it isn't an error to pass `--help` /// This must be registered so that it isn't an error to pass `--help`
help: bool = false, help: bool = false,
@ -15,9 +15,9 @@ pub const Options = struct {
} }
}; };
/// The `help` command shows general help about Ghostty. You can also /// The `help` command shows general help about Ghostty. You can also specify
/// specify `--help` or `-h` along with any action such as `+list-themes` /// `--help` or `-h` along with any action such as `+list-themes` to see help
/// to see help for a specific action. /// for a specific action.
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {
var opts: Options = .{}; var opts: Options = .{};
defer opts.deinit(); defer opts.deinit();

View File

@ -15,7 +15,7 @@ pub const Options = struct {
} }
}; };
/// The "list-colors" command is used to list all the named RGB colors in /// The `list-colors` command is used to list all the named RGB colors in
/// Ghostty. /// Ghostty.
pub fn run(alloc: std.mem.Allocator) !u8 { pub fn run(alloc: std.mem.Allocator) !u8 {
var opts: Options = .{}; var opts: Options = .{};

View File

@ -35,22 +35,21 @@ pub const Config = struct {
} }
}; };
/// The list-fonts command is used to list all the available fonts for Ghostty. /// The `list-fonts` command is used to list all the available fonts for
/// This uses the exact same font discovery mechanism Ghostty uses to find /// Ghostty. This uses the exact same font discovery mechanism Ghostty uses to
/// fonts to use. /// find fonts to use.
/// ///
/// When executed with no arguments, this will list all available fonts, /// When executed with no arguments, this will list all available fonts, sorted
/// sorted by family name, then font name. If a family name is given /// by family name, then font name. If a family name is given with `--family`,
/// with "--family", the sorting will be disabled and the results instead /// the sorting will be disabled and the results instead will be shown in the
/// will be shown in the same priority order Ghostty would use to pick a /// same priority order Ghostty would use to pick a font.
/// font.
/// ///
/// The "--family" argument can be used to filter results to a specific family. /// The `--family` argument can be used to filter results to a specific family.
/// The family handling is identical to the "font-familiy" set of Ghostty /// The family handling is identical to the `font-familiy` set of Ghostty
/// configuration values, so this can be used to debug why your desired font /// configuration values, so this can be used to debug why your desired font may
/// may not be loading. /// not be loading.
/// ///
/// The "--bold" and "--italic" arguments can be used to filter results to /// The `--bold` and `--italic` arguments can be used to filter results to
/// specific styles. It is not guaranteed that only those styles are returned, /// specific styles. It is not guaranteed that only those styles are returned,
/// it will just prioriiize fonts that match those styles. /// it will just prioriiize fonts that match those styles.
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {

View File

@ -23,16 +23,16 @@ pub const Options = struct {
} }
}; };
/// The "list-keybinds" command is used to list all the available keybinds /// The `list-keybinds` command is used to list all the available keybinds for
/// for Ghostty. /// Ghostty.
/// ///
/// When executed without any arguments this will list the current keybinds /// When executed without any arguments this will list the current keybinds
/// loaded by the config file. If no config file is found or there aren't any /// loaded by the config file. If no config file is found or there aren't any
/// changes to the keybinds it will print out the default ones configured for /// changes to the keybinds it will print out the default ones configured for
/// Ghostty /// Ghostty
/// ///
/// The "--default" argument will print out all the default keybinds /// The `--default` argument will print out all the default keybinds configured
/// configured for Ghostty /// for Ghostty
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {
var opts: Options = .{}; var opts: Options = .{};
defer opts.deinit(); defer opts.deinit();

View File

@ -19,15 +19,15 @@ pub const Options = struct {
} }
}; };
/// The "list-themes" command is used to list all the available themes /// The `list-themes` command is used to list all the available themes for
/// for Ghostty. /// Ghostty.
/// ///
/// Themes require that Ghostty have access to the resources directory. /// Themes require that Ghostty have access to the resources directory. On macOS
/// On macOS this is embedded in the app bundle. On Linux, this is usually /// this is embedded in the app bundle. On Linux, this is usually in `/usr/
/// in `/usr/share/ghostty`. If you're compiling from source, this is the /// share/ghostty`. If you're compiling from source, this is the `zig-out/share/
/// `zig-out/share/ghostty` directory. You can also set the `GHOSTTY_RESOURCES_DIR` /// ghostty` directory. You can also set the `GHOSTTY_RESOURCES_DIR` environment
/// environment variable to point to the resources directory. Themes /// variable to point to the resources directory. Themes live in the `themes`
/// live in the `themes` subdirectory of the resources directory. /// subdirectory of the resources directory.
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {
var opts: Options = .{}; var opts: Options = .{};
defer opts.deinit(); defer opts.deinit();

View File

@ -21,15 +21,15 @@ pub const Options = struct {
_ = self; _ = self;
} }
/// Enables "-h" and "--help" to work. /// Enables `-h` and `--help` to work.
pub fn help(self: Options) !void { pub fn help(self: Options) !void {
_ = self; _ = self;
return Action.help_error; return Action.help_error;
} }
}; };
/// The "show-config" command shows the current configuration in a valid /// The `show-config` command shows the current configuration in a valid Ghostty
/// Ghostty configuration file format. /// configuration file format.
/// ///
/// When executed without any arguments this will output the current /// When executed without any arguments this will output the current
/// configuration that is different from the default configuration. If you're /// configuration that is different from the default configuration. If you're
@ -38,24 +38,23 @@ pub const Options = struct {
/// If you are a new user and want to see all available options with /// If you are a new user and want to see all available options with
/// documentation, run `ghostty +show-config --default --docs`. /// documentation, run `ghostty +show-config --default --docs`.
/// ///
/// The output is not in any specific order, but the order should be /// The output is not in any specific order, but the order should be consistent
/// consistent between runs. The output is not guaranteed to be exactly /// between runs. The output is not guaranteed to be exactly match the input
/// match the input configuration files, but it will result in the same /// configuration files, but it will result in the same behavior. Comments,
/// behavior. Comments, whitespace, and other formatting is not preserved /// whitespace, and other formatting is not preserved from user configuration
/// from user configuration files. /// files.
/// ///
/// Flags: /// Flags:
/// ///
/// - "--default": Show the default configuration instead of loading /// * `--default`: Show the default configuration instead of loading
/// the user configuration. /// the user configuration.
/// ///
/// - "--changes-only": Only show the options that have been changed /// * `--changes-only`: Only show the options that have been changed
/// from the default. This has no effect if "--default" is specified. /// from the default. This has no effect if `--default` is specified.
/// ///
/// - "--docs": Print the documentation above each option as a comment, /// * `--docs`: Print the documentation above each option as a comment,
/// This is very noisy but is very useful to learn about available /// This is very noisy but is very useful to learn about available
/// options, especially paired with "--default". /// options, especially paired with `--default`.
///
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {
var opts: Options = .{}; var opts: Options = .{};
defer opts.deinit(); defer opts.deinit();

View File

@ -12,15 +12,14 @@ pub const Options = struct {
_ = self; _ = self;
} }
/// Enables "-h" and "--help" to work. /// Enables `-h` and `--help` to work.
pub fn help(self: Options) !void { pub fn help(self: Options) !void {
_ = self; _ = self;
return Action.help_error; return Action.help_error;
} }
}; };
/// The `version` command is used to display information /// The `version` command is used to display information about Ghostty.
/// about Ghostty.
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {
var opts: Options = .{}; var opts: Options = .{};
defer opts.deinit(); defer opts.deinit();
@ -34,6 +33,7 @@ pub fn run(alloc: Allocator) !u8 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
try stdout.print("Ghostty {s}\n\n", .{build_config.version_string}); try stdout.print("Ghostty {s}\n\n", .{build_config.version_string});
try stdout.print("Build Config\n", .{}); try stdout.print("Build Config\n", .{});
try stdout.print(" - Zig version: {s}\n", .{builtin.zig_version_string});
try stdout.print(" - build mode : {}\n", .{builtin.mode}); try stdout.print(" - build mode : {}\n", .{builtin.mode});
try stdout.print(" - app runtime: {}\n", .{build_config.app_runtime}); try stdout.print(" - app runtime: {}\n", .{build_config.app_runtime});
try stdout.print(" - font engine: {}\n", .{build_config.font_backend}); try stdout.print(" - font engine: {}\n", .{build_config.font_backend});

File diff suppressed because it is too large Load Diff

13
src/mdgen_ghostty_1.zig Normal file
View File

@ -0,0 +1,13 @@
const std = @import("std");
const gen = @import("build/mdgen/mdgen.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
const writer = std.io.getStdOut().writer();
try gen.substitute(alloc, @embedFile("build/mdgen/ghostty_1_header.md"), writer);
try gen.genActions(writer);
try gen.genConfig(writer, true);
try gen.substitute(alloc, @embedFile("build/mdgen/ghostty_1_footer.md"), writer);
}

12
src/mdgen_ghostty_5.zig Normal file
View File

@ -0,0 +1,12 @@
const std = @import("std");
const gen = @import("build/mdgen/mdgen.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
const output = std.io.getStdOut().writer();
try gen.substitute(alloc, @embedFile("build/mdgen/ghostty_5_header.md"), output);
try gen.genConfig(output, false);
try gen.substitute(alloc, @embedFile("build/mdgen/ghostty_5_footer.md"), output);
}