mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-28 19:21:38 +02:00
Generate man pages for kitten and all its sub-commands recursively
Fixes #6808
This commit is contained in:
104
docs/conf.py
104
docs/conf.py
@@ -7,6 +7,7 @@
|
|||||||
# full list see the documentation:
|
# full list see the documentation:
|
||||||
# https://www.sphinx-doc.org/en/master/config
|
# https://www.sphinx-doc.org/en/master/config
|
||||||
|
|
||||||
|
import glob
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -27,7 +28,7 @@ if kitty_src not in sys.path:
|
|||||||
sys.path.insert(0, kitty_src)
|
sys.path.insert(0, kitty_src)
|
||||||
|
|
||||||
from kitty.conf.types import Definition, expand_opt_references # noqa
|
from kitty.conf.types import Definition, expand_opt_references # noqa
|
||||||
from kitty.constants import str_version, website_url # noqa
|
from kitty.constants import str_version, website_url # noqa
|
||||||
|
|
||||||
# config {{{
|
# config {{{
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
@@ -176,8 +177,8 @@ manpages_url = 'https://man7.org/linux/man-pages/man{section}/{page}.{section}.h
|
|||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('invocation', 'kitty', 'kitty Documentation', [author], 1),
|
('invocation', 'kitty', 'The fast, feature rich terminal emulator', [author], 1),
|
||||||
('conf', 'kitty.conf', 'kitty terminal emulator configuration file', [author], 5)
|
('conf', 'kitty.conf', 'Configuration file for kitty', [author], 5)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -549,10 +550,13 @@ def add_html_context(app: Any, pagename: str, templatename: str, context: Any, d
|
|||||||
@lru_cache
|
@lru_cache
|
||||||
def monkeypatch_man_writer() -> None:
|
def monkeypatch_man_writer() -> None:
|
||||||
'''
|
'''
|
||||||
Monkeypatch the docutils man translator to output better tables
|
Monkeypatch the docutils man translator to be nicer
|
||||||
'''
|
'''
|
||||||
from docutils.writers.manpage import Table, Translator
|
from docutils.writers.manpage import Table, Translator
|
||||||
|
from docutils.nodes import Element
|
||||||
|
from sphinx.writers.manpage import ManualPageTranslator
|
||||||
|
|
||||||
|
# Generate nicer tables https://sourceforge.net/p/docutils/bugs/475/
|
||||||
class PatchedTable(Table): # type: ignore
|
class PatchedTable(Table): # type: ignore
|
||||||
_options: list[str]
|
_options: list[str]
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -572,15 +576,100 @@ def monkeypatch_man_writer() -> None:
|
|||||||
del ans[3] # top border
|
del ans[3] # top border
|
||||||
del ans[-2] # bottom border
|
del ans[-2] # bottom border
|
||||||
return ans
|
return ans
|
||||||
def visit_table(self: Translator, node: object) -> None:
|
def visit_table(self: ManualPageTranslator, node: object) -> None:
|
||||||
setattr(self, '_active_table', PatchedTable())
|
setattr(self, '_active_table', PatchedTable())
|
||||||
setattr(Translator, 'visit_table', visit_table)
|
setattr(ManualPageTranslator, 'visit_table', visit_table)
|
||||||
|
|
||||||
|
# Improve header generation
|
||||||
|
def header(self: ManualPageTranslator) -> str:
|
||||||
|
di = getattr(self, '_docinfo')
|
||||||
|
di['ktitle'] = di['title'].replace('_', '-')
|
||||||
|
th = (".TH \"%(ktitle)s\" %(manual_section)s"
|
||||||
|
" \"%(date)s\" \"%(version)s\"") % di
|
||||||
|
if di["manual_group"]:
|
||||||
|
th += " \"%(manual_group)s\"" % di
|
||||||
|
th += "\n"
|
||||||
|
sh_tmpl: str = (".SH Name\n"
|
||||||
|
"%(ktitle)s \\- %(subtitle)s\n")
|
||||||
|
return th + sh_tmpl % di # type: ignore
|
||||||
|
|
||||||
|
setattr(ManualPageTranslator, 'header', header)
|
||||||
|
|
||||||
|
def visit_image(self: ManualPageTranslator, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_image(self: ManualPageTranslator, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_figure(self: ManualPageTranslator, node: Element) -> None:
|
||||||
|
self.body.append(' (images not supported)\n')
|
||||||
|
Translator.depart_figure(self, node)
|
||||||
|
|
||||||
|
setattr(ManualPageTranslator, 'visit_image', visit_image)
|
||||||
|
setattr(ManualPageTranslator, 'depart_image', depart_image)
|
||||||
|
setattr(ManualPageTranslator, 'depart_figure', depart_figure)
|
||||||
|
|
||||||
|
orig_astext = Translator.astext
|
||||||
|
def astext(self: Translator) -> str:
|
||||||
|
b = []
|
||||||
|
for line in self.body:
|
||||||
|
if line.startswith('.SH'):
|
||||||
|
x, y = line.split(' ', 1)
|
||||||
|
parts = y.splitlines(keepends=True)
|
||||||
|
parts[0] = parts[0].capitalize()
|
||||||
|
line = x + ' ' + '\n'.join(parts)
|
||||||
|
b.append(line)
|
||||||
|
self.body = b
|
||||||
|
return orig_astext(self)
|
||||||
|
setattr(Translator, 'astext', astext)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_man_pages() -> None:
|
||||||
|
from kittens.runner import get_kitten_cli_docs
|
||||||
|
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
for x in glob.glob(os.path.join(base, 'docs/kittens/*.rst')):
|
||||||
|
kn = os.path.basename(x).rpartition('.')[0]
|
||||||
|
if kn == 'custom':
|
||||||
|
continue
|
||||||
|
cd = get_kitten_cli_docs(kn) or {}
|
||||||
|
khn = kn.replace('_', '-')
|
||||||
|
man_pages.append((f'kittens/{kn}', 'kitten-' + khn, cd.get('short_desc', 'kitten Documentation'), [author], 1))
|
||||||
|
monkeypatch_man_writer()
|
||||||
|
|
||||||
|
|
||||||
|
def build_extra_man_pages() -> None:
|
||||||
|
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
kitten = os.path.join(base, 'kitty/launcher/kitten')
|
||||||
|
if not os.path.exists(kitten):
|
||||||
|
raise Exception('The kitten binary is not built cannot generate man pages')
|
||||||
|
raw = subprocess.check_output([kitten, '-h']).decode()
|
||||||
|
started = 0
|
||||||
|
names = set()
|
||||||
|
for line in raw.splitlines():
|
||||||
|
if line.strip() == '@':
|
||||||
|
started = len(line.rstrip()[:-1])
|
||||||
|
q = line.strip()
|
||||||
|
if started and len(q.split()) == 1 and not q.startswith('-') and ':' not in q:
|
||||||
|
if len(line) - len(line.lstrip()) == started:
|
||||||
|
if not os.path.exists(os.path.join(base, f'docs/kittens/{q}.rst')):
|
||||||
|
names.add(q)
|
||||||
|
cwd = os.path.join(base, 'docs/_build/man')
|
||||||
|
subprocess.check_call([kitten, '__generate_man_pages__'], cwd=cwd)
|
||||||
|
subprocess.check_call([kitten, '__generate_man_pages__'] + list(names), cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
|
if building_man_pages:
|
||||||
|
setup_man_pages()
|
||||||
|
|
||||||
|
|
||||||
|
def build_finished(*a: Any, **kw: Any) -> None:
|
||||||
|
if building_man_pages:
|
||||||
|
build_extra_man_pages()
|
||||||
|
|
||||||
|
|
||||||
def setup(app: Any) -> None:
|
def setup(app: Any) -> None:
|
||||||
os.makedirs('generated/conf', exist_ok=True)
|
os.makedirs('generated/conf', exist_ok=True)
|
||||||
from kittens.runner import all_kitten_names
|
from kittens.runner import all_kitten_names
|
||||||
monkeypatch_man_writer()
|
|
||||||
kn = all_kitten_names()
|
kn = all_kitten_names()
|
||||||
write_cli_docs(kn)
|
write_cli_docs(kn)
|
||||||
write_remote_control_protocol_docs()
|
write_remote_control_protocol_docs()
|
||||||
@@ -589,6 +678,7 @@ def setup(app: Any) -> None:
|
|||||||
app.connect('source-read', replace_string)
|
app.connect('source-read', replace_string)
|
||||||
app.add_config_value('analytics_id', '', 'env')
|
app.add_config_value('analytics_id', '', 'env')
|
||||||
app.connect('html-page-context', add_html_context)
|
app.connect('html-page-context', add_html_context)
|
||||||
|
app.connect('build-finished', build_finished)
|
||||||
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
|
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
|
||||||
app.add_role('link', link_role)
|
app.add_role('link', link_role)
|
||||||
app.add_role('commit', commit_role)
|
app.add_role('commit', commit_role)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
broadcast
|
broadcast
|
||||||
==================================================
|
==================================================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
*Type text in all kitty windows simultaneously*
|
*Type text in all kitty windows simultaneously*
|
||||||
|
|
||||||
The ``broadcast`` kitten can be used to type text simultaneously in all
|
The ``broadcast`` kitten can be used to type text simultaneously in all
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
clipboard
|
clipboard
|
||||||
==================================================
|
==================================================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
*Copy/paste to the system clipboard from shell scripts*
|
*Copy/paste to the system clipboard from shell scripts*
|
||||||
|
|
||||||
.. highlight:: sh
|
.. highlight:: sh
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Hints
|
Hints
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
|kitty| has a *hints mode* to select and act on arbitrary text snippets
|
|kitty| has a *hints mode* to select and act on arbitrary text snippets
|
||||||
currently visible on the screen. For example, you can press :sc:`open_url`
|
currently visible on the screen. For example, you can press :sc:`open_url`
|
||||||
to choose any URL visible on the screen and then open it using your default web
|
to choose any URL visible on the screen and then open it using your default web
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Hyperlinked grep
|
Hyperlinked grep
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
As of ripgrep versions newer that 13.0 it supports hyperlinks
|
As of ripgrep versions newer that 13.0 it supports hyperlinks
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
icat
|
icat
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
*Display images in the terminal*
|
*Display images in the terminal*
|
||||||
|
|
||||||
The ``icat`` kitten can be used to display arbitrary images in the |kitty|
|
The ``icat`` kitten can be used to display arbitrary images in the |kitty|
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ Draw a GPU accelerated dock panel on your desktop
|
|||||||
|
|
||||||
.. highlight:: sh
|
.. highlight:: sh
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
You can use this kitten to draw a GPU accelerated panel on the edge of your
|
You can use this kitten to draw a GPU accelerated panel on the edge of your
|
||||||
screen, that shows the output from an arbitrary terminal program.
|
screen, that shows the output from an arbitrary terminal program.
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Query terminal
|
Query terminal
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
This kitten is used to query |kitty| from terminal programs about version, values
|
This kitten is used to query |kitty| from terminal programs about version, values
|
||||||
of various runtime options controlling its features, etc.
|
of various runtime options controlling its features, etc.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Remote files
|
Remote files
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
|kitty| has the ability to easily *Edit*, *Open* or *Download* files from a
|
|kitty| has the ability to easily *Edit*, *Open* or *Download* files from a
|
||||||
computer into which you are SSHed. In your SSH session run::
|
computer into which you are SSHed. In your SSH session run::
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
Truly convenient SSH
|
Truly convenient SSH
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
----------------
|
||||||
|
|
||||||
* Automatic :ref:`shell_integration` on remote hosts
|
* Automatic :ref:`shell_integration` on remote hosts
|
||||||
|
|
||||||
* Easily :ref:`clone local shell/editor config <real_world_ssh_kitten_config>` on remote hosts
|
* Easily :ref:`clone local shell/editor config <real_world_ssh_kitten_config>` on remote hosts
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Changing kitty colors
|
Changing kitty colors
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
The themes kitten allows you to easily change color themes, from a collection of
|
The themes kitten allows you to easily change color themes, from a collection of
|
||||||
over three hundred pre-built themes available at `kitty-themes
|
over three hundred pre-built themes available at `kitty-themes
|
||||||
<https://github.com/kovidgoyal/kitty-themes>`_. To use it, simply run::
|
<https://github.com/kovidgoyal/kitty-themes>`_. To use it, simply run::
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Transfer files
|
Transfer files
|
||||||
================
|
================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
.. versionadded:: 0.30.0
|
.. versionadded:: 0.30.0
|
||||||
|
|
||||||
.. _rsync: https://en.wikipedia.org/wiki/Rsync
|
.. _rsync: https://en.wikipedia.org/wiki/Rsync
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Unicode input
|
Unicode input
|
||||||
================
|
================
|
||||||
|
|
||||||
|
.. only:: man
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
You can input Unicode characters by name, hex code, recently used and even an
|
You can input Unicode characters by name, hex code, recently used and even an
|
||||||
editable favorites list. Press :sc:`input_unicode_character` to start the
|
editable favorites list. Press :sc:`input_unicode_character` to start the
|
||||||
unicode input kitten, shown below.
|
unicode input kitten, shown below.
|
||||||
|
|||||||
@@ -160,3 +160,4 @@ elif __name__ == '__doc__':
|
|||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = OPTIONS
|
cd['options'] = OPTIONS
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['short_desc'] = 'Broadcast typed text to kitty windows'
|
||||||
|
|||||||
@@ -149,3 +149,4 @@ elif __name__ == '__doc__':
|
|||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = OPTIONS
|
cd['options'] = OPTIONS
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['short_desc'] = help_text
|
||||||
|
|||||||
@@ -258,3 +258,4 @@ elif __name__ == '__doc__':
|
|||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = options_spec
|
cd['options'] = options_spec
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['short_desc'] = 'Query the terminal for various capabilities'
|
||||||
|
|||||||
@@ -11,12 +11,6 @@ import (
|
|||||||
var _ = fmt.Print
|
var _ = fmt.Print
|
||||||
var _ = os.Getenv
|
var _ = os.Getenv
|
||||||
|
|
||||||
func (self *Completions) add_group(group *MatchGroup) {
|
|
||||||
if len(group.Matches) > 0 {
|
|
||||||
self.Groups = append(self.Groups, group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Completions) add_options_group(options []*Option, word string) {
|
func (self *Completions) add_options_group(options []*Option, word string) {
|
||||||
group := self.AddMatchGroup("Options")
|
group := self.AddMatchGroup("Options")
|
||||||
if strings.HasPrefix(word, "--") {
|
if strings.HasPrefix(word, "--") {
|
||||||
@@ -122,7 +116,6 @@ func complete_word(word string, completions *Completions, only_args_allowed bool
|
|||||||
if cmd.ArgCompleter != nil {
|
if cmd.ArgCompleter != nil {
|
||||||
cmd.ArgCompleter(completions, word, arg_num)
|
cmd.ArgCompleter(completions, word, arg_num)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func completion_parse_args(cmd *Command, words []string, completions *Completions) {
|
func completion_parse_args(cmd *Command, words []string, completions *Completions) {
|
||||||
|
|||||||
@@ -8,12 +8,15 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"kitty"
|
"kitty"
|
||||||
"kitty/tools/cli/markup"
|
"kitty/tools/cli/markup"
|
||||||
"kitty/tools/tty"
|
"kitty/tools/tty"
|
||||||
|
"kitty/tools/utils"
|
||||||
"kitty/tools/utils/style"
|
"kitty/tools/utils/style"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,6 +65,37 @@ func (self *Command) FormatSubCommands(output io.Writer, formatter *markup.Conte
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Option) FormatOptionForMan(output io.Writer) {
|
||||||
|
fmt.Fprintln(output, ".TP")
|
||||||
|
fmt.Fprint(output, ".BI \"")
|
||||||
|
for i, a := range self.Aliases {
|
||||||
|
fmt.Fprint(output, a.String())
|
||||||
|
if i != len(self.Aliases)-1 {
|
||||||
|
fmt.Fprint(output, ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprint(output, "\" ")
|
||||||
|
defval := self.Default
|
||||||
|
switch self.OptionType {
|
||||||
|
case StringOption:
|
||||||
|
if self.IsList {
|
||||||
|
defval = ""
|
||||||
|
}
|
||||||
|
case BoolOption, CountOption:
|
||||||
|
defval = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if defval != "" {
|
||||||
|
fmt.Fprintf(output, "\" [=%s]\"", escape_text_for_man(defval))
|
||||||
|
}
|
||||||
|
fmt.Fprintln(output)
|
||||||
|
fmt.Fprintln(output, escape_help_for_man(self.Help))
|
||||||
|
if self.Choices != nil {
|
||||||
|
fmt.Fprintln(output)
|
||||||
|
fmt.Fprintln(output, "Choices: "+strings.Join(self.Choices, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Option) FormatOption(output io.Writer, formatter *markup.Context, screen_width int) {
|
func (self *Option) FormatOption(output io.Writer, formatter *markup.Context, screen_width int) {
|
||||||
fmt.Fprint(output, " ")
|
fmt.Fprint(output, " ")
|
||||||
for i, a := range self.Aliases {
|
for i, a := range self.Aliases {
|
||||||
@@ -101,6 +135,82 @@ func ShowHelpInPager(text string) {
|
|||||||
_ = pager.Run()
|
_ = pager.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Command) GenerateManPages(level int, recurse bool) (err error) {
|
||||||
|
var names []string
|
||||||
|
p := self
|
||||||
|
for p != nil {
|
||||||
|
names = append(names, p.Name)
|
||||||
|
p = p.Parent
|
||||||
|
}
|
||||||
|
slices.Reverse(names)
|
||||||
|
name := strings.Join(names, "-")
|
||||||
|
outf, err := os.Create(fmt.Sprintf("%s.%d", name, level))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outf.Close()
|
||||||
|
fmt.Fprintf(outf, `.TH "%s" "1" "%s" "%s" "%s"`, name, time.Now().Format("Jan 02, 2006"), kitty.VersionString, "kitten Manual")
|
||||||
|
fmt.Fprintln(outf)
|
||||||
|
fmt.Fprintln(outf, ".SH Name")
|
||||||
|
fmt.Fprintln(outf, name, "\\-", escape_text_for_man(self.ShortDescription))
|
||||||
|
fmt.Fprintln(outf, ".SH Usage")
|
||||||
|
fmt.Fprintln(outf, ".SY", `"`+self.CommandStringForUsage()+` `+self.Usage+`"`)
|
||||||
|
fmt.Fprintln(outf, ".YS")
|
||||||
|
if self.HelpText != "" {
|
||||||
|
fmt.Fprintln(outf, ".SH Description")
|
||||||
|
fmt.Fprintln(outf, escape_help_for_man(self.HelpText))
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.HasVisibleSubCommands() {
|
||||||
|
for _, g := range self.SubCommandGroups {
|
||||||
|
if !g.HasVisibleSubCommands() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
title := g.Title
|
||||||
|
if title == "" {
|
||||||
|
title = "Commands"
|
||||||
|
}
|
||||||
|
fmt.Fprintln(outf, ".SH", title)
|
||||||
|
|
||||||
|
for _, c := range utils.Sort(g.SubCommands, func(a, b *Command) int { return strings.Compare(a.Name, b.Name) }) {
|
||||||
|
if c.Hidden {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if recurse {
|
||||||
|
if err = c.GenerateManPages(level, recurse); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintln(outf, ".TP", "2")
|
||||||
|
fmt.Fprintln(outf, c.Name)
|
||||||
|
fmt.Fprintln(outf, escape_text_for_man(c.ShortDescription)+".", "See: ")
|
||||||
|
fmt.Fprintf(outf, ".MR %s %d\n", name+"-"+c.Name, level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintln(outf, ".PP")
|
||||||
|
fmt.Fprintln(outf, "Get help for an individual command by running:")
|
||||||
|
fmt.Fprintln(outf, ".SY", self.CommandStringForUsage())
|
||||||
|
fmt.Fprintln(outf, "command", "-h")
|
||||||
|
fmt.Fprintln(outf, ".YS")
|
||||||
|
}
|
||||||
|
|
||||||
|
group_titles, gmap := self.GetVisibleOptions()
|
||||||
|
if len(group_titles) > 0 {
|
||||||
|
for _, title := range group_titles {
|
||||||
|
ptitle := title
|
||||||
|
if title == "" {
|
||||||
|
ptitle = "Options"
|
||||||
|
}
|
||||||
|
fmt.Fprintln(outf, ".SH", ptitle)
|
||||||
|
for _, opt := range gmap[title] {
|
||||||
|
opt.FormatOptionForMan(outf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Command) ShowHelpWithCommandString(cs string) {
|
func (self *Command) ShowHelpWithCommandString(cs string) {
|
||||||
formatter := markup.New(tty.IsTerminal(os.Stdout.Fd()))
|
formatter := markup.New(tty.IsTerminal(os.Stdout.Fd()))
|
||||||
screen_width := 80
|
screen_width := 80
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func New(allow_escape_codes bool) *Context {
|
|||||||
return &ans
|
return &ans
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove_backslash_escapes(text string) string {
|
func Remove_backslash_escapes(text string) string {
|
||||||
// see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#escaping-mechanism
|
// see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#escaping-mechanism
|
||||||
out := strings.Builder{}
|
out := strings.Builder{}
|
||||||
prev_was_slash := false
|
prev_was_slash := false
|
||||||
@@ -75,11 +75,11 @@ func remove_backslash_escapes(text string) string {
|
|||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func replace_all_rst_roles(str string, repl func(rst_format_match) string) string {
|
func ReplaceAllRSTRoles(str string, repl func(Rst_format_match) string) string {
|
||||||
var m rst_format_match
|
var m Rst_format_match
|
||||||
rf := func(full_match string, groupdict map[string]utils.SubMatch) string {
|
rf := func(full_match string, groupdict map[string]utils.SubMatch) string {
|
||||||
m.payload = groupdict["payload"].Text
|
m.Payload = groupdict["payload"].Text
|
||||||
m.role = groupdict["role"].Text
|
m.Role = groupdict["role"].Text
|
||||||
return repl(m)
|
return repl(m)
|
||||||
}
|
}
|
||||||
return utils.ReplaceAll(utils.MustCompile(":(?P<role>[a-z]+):(?:(?:`(?P<payload>[^`]+)`)|(?:'(?P<payload>[^']+)'))"), str, rf)
|
return utils.ReplaceAll(utils.MustCompile(":(?P<role>[a-z]+):(?:(?:`(?P<payload>[^`]+)`)|(?:'(?P<payload>[^']+)'))"), str, rf)
|
||||||
@@ -103,35 +103,35 @@ func (self *Context) hyperlink_for_path(path string, text string) string {
|
|||||||
return self.hyperlink_for_url(url, text)
|
return self.hyperlink_for_url(url, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func text_and_target(x string) (text string, target string) {
|
func Text_and_target(x string) (text string, target string) {
|
||||||
parts := strings.SplitN(x, "<", 2)
|
parts := strings.SplitN(x, "<", 2)
|
||||||
text = strings.TrimSpace(parts[0])
|
text = strings.TrimSpace(parts[0])
|
||||||
target = strings.TrimRight(parts[len(parts)-1], ">")
|
target = strings.TrimRight(parts[len(parts)-1], ">")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type rst_format_match struct {
|
type Rst_format_match struct {
|
||||||
role, payload string
|
Role, Payload string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Context) link(x string) string {
|
func (self *Context) link(x string) string {
|
||||||
text, url := text_and_target(x)
|
text, url := Text_and_target(x)
|
||||||
return self.hyperlink_for_url(url, text)
|
return self.hyperlink_for_url(url, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Context) ref_hyperlink(x string, prefix string) string {
|
func (self *Context) ref_hyperlink(x string, prefix string) string {
|
||||||
text, target := text_and_target(x)
|
text, target := Text_and_target(x)
|
||||||
url := "kitty+doc://" + utils.Hostname() + "/#ref=" + prefix + target
|
url := "kitty+doc://" + utils.Hostname() + "/#ref=" + prefix + target
|
||||||
text = replace_all_rst_roles(text, func(group rst_format_match) string {
|
text = ReplaceAllRSTRoles(text, func(group Rst_format_match) string {
|
||||||
return group.payload
|
return group.Payload
|
||||||
})
|
})
|
||||||
return self.hyperlink_for_url(url, text)
|
return self.hyperlink_for_url(url, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Context) Prettify(text string) string {
|
func (self *Context) Prettify(text string) string {
|
||||||
return replace_all_rst_roles(text, func(group rst_format_match) string {
|
return ReplaceAllRSTRoles(text, func(group Rst_format_match) string {
|
||||||
val := group.payload
|
val := group.Payload
|
||||||
switch group.role {
|
switch group.Role {
|
||||||
case "file":
|
case "file":
|
||||||
if val == "kitty.conf" && self.fmt_ctx.AllowEscapeCodes {
|
if val == "kitty.conf" && self.fmt_ctx.AllowEscapeCodes {
|
||||||
path := filepath.Join(utils.ConfigDir(), val)
|
path := filepath.Join(utils.ConfigDir(), val)
|
||||||
@@ -141,7 +141,7 @@ func (self *Context) Prettify(text string) string {
|
|||||||
case "env", "envvar":
|
case "env", "envvar":
|
||||||
return self.ref_hyperlink(val, "envvar-")
|
return self.ref_hyperlink(val, "envvar-")
|
||||||
case "doc":
|
case "doc":
|
||||||
text, target := text_and_target(val)
|
text, target := Text_and_target(val)
|
||||||
no_title := text == target
|
no_title := text == target
|
||||||
target = strings.Trim(target, "/")
|
target = strings.Trim(target, "/")
|
||||||
if title, ok := kitty.DocTitleMap[target]; ok && no_title {
|
if title, ok := kitty.DocTitleMap[target]; ok && no_title {
|
||||||
@@ -163,7 +163,7 @@ func (self *Context) Prettify(text string) string {
|
|||||||
case "term":
|
case "term":
|
||||||
return self.ref_hyperlink(val, "term-")
|
return self.ref_hyperlink(val, "term-")
|
||||||
case "code":
|
case "code":
|
||||||
return self.Code(remove_backslash_escapes(val))
|
return self.Code(Remove_backslash_escapes(val))
|
||||||
case "link":
|
case "link":
|
||||||
return self.link(val)
|
return self.link(val)
|
||||||
case "option":
|
case "option":
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"kitty/tools/cli/markup"
|
||||||
"kitty/tools/utils"
|
"kitty/tools/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -193,6 +194,168 @@ func indent_of_line(x string) int {
|
|||||||
return len(x) - len(strings.TrimLeft(x, " \n\t\v\f"))
|
return len(x) - len(strings.TrimLeft(x, " \n\t\v\f"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func escape_text_for_man(raw string) string {
|
||||||
|
italic := func(x string) string {
|
||||||
|
return "\n.I " + x
|
||||||
|
}
|
||||||
|
bold := func(x string) string {
|
||||||
|
return "\n.B " + x
|
||||||
|
}
|
||||||
|
text_without_target := func(val string) string {
|
||||||
|
text, target := markup.Text_and_target(val)
|
||||||
|
no_title := text == target
|
||||||
|
if no_title {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
ref_hyperlink := func(val, prefix string) string {
|
||||||
|
return text_without_target(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw = markup.ReplaceAllRSTRoles(raw, func(group markup.Rst_format_match) string {
|
||||||
|
val := group.Payload
|
||||||
|
switch group.Role {
|
||||||
|
case "file":
|
||||||
|
return italic(val)
|
||||||
|
case "env", "envvar":
|
||||||
|
return bold(val)
|
||||||
|
case "doc":
|
||||||
|
return text_without_target(val)
|
||||||
|
case "iss":
|
||||||
|
return "Issue #" + val
|
||||||
|
case "pull":
|
||||||
|
return "PR #" + val
|
||||||
|
case "disc":
|
||||||
|
return "Discussion #" + val
|
||||||
|
case "ref":
|
||||||
|
return ref_hyperlink(val, "")
|
||||||
|
case "ac":
|
||||||
|
return ref_hyperlink(val, "action-")
|
||||||
|
case "term":
|
||||||
|
return ref_hyperlink(val, "term-")
|
||||||
|
case "code":
|
||||||
|
return markup.Remove_backslash_escapes(val)
|
||||||
|
case "link":
|
||||||
|
return text_without_target(val)
|
||||||
|
case "option":
|
||||||
|
idx := strings.LastIndex(val, "--")
|
||||||
|
if idx < 0 {
|
||||||
|
idx = strings.Index(val, "-")
|
||||||
|
}
|
||||||
|
if idx > -1 {
|
||||||
|
val = strings.TrimSuffix(val[idx:], ">")
|
||||||
|
}
|
||||||
|
return bold(val)
|
||||||
|
case "opt":
|
||||||
|
return bold(val)
|
||||||
|
case "yellow":
|
||||||
|
return val
|
||||||
|
case "blue":
|
||||||
|
return val
|
||||||
|
case "green":
|
||||||
|
return val
|
||||||
|
case "cyan":
|
||||||
|
return val
|
||||||
|
case "magenta":
|
||||||
|
return val
|
||||||
|
case "emph":
|
||||||
|
return val
|
||||||
|
default:
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sb := strings.Builder{}
|
||||||
|
sb.Grow(2 * len(raw))
|
||||||
|
replacements := map[rune]string{
|
||||||
|
'"': `\[dq]`, '\'': `\[aq]`, '-': `\-`, '\\': `\e`, '^': `\(ha`, '`': `\(ga`, '~': `\(ti`,
|
||||||
|
}
|
||||||
|
for _, ch := range raw {
|
||||||
|
if rep, found := replacements[ch]; found {
|
||||||
|
sb.WriteString(rep)
|
||||||
|
} else {
|
||||||
|
sb.WriteRune(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func escape_help_for_man(raw string) string {
|
||||||
|
help := strings.Builder{}
|
||||||
|
help.Grow(len(raw) + 256)
|
||||||
|
prev_indent := 0
|
||||||
|
in_code_block := false
|
||||||
|
lines := utils.Splitlines(raw)
|
||||||
|
|
||||||
|
handle_non_empty_line := func(i int, line string) int {
|
||||||
|
if strings.TrimSpace(line) == "#placeholder_for_formatting#" {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, ".. code::") {
|
||||||
|
in_code_block = true
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
current_indent := indent_of_line(line)
|
||||||
|
if current_indent > 1 {
|
||||||
|
if prev_indent == 0 {
|
||||||
|
help.WriteString("\n")
|
||||||
|
} else {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_indent = current_indent
|
||||||
|
if help.Len() > 0 && !strings.HasSuffix(help.String(), "\n") {
|
||||||
|
help.WriteString(" ")
|
||||||
|
}
|
||||||
|
help.WriteString(line)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_empty_line := func(i int, line string) int {
|
||||||
|
prev_indent = 0
|
||||||
|
help.WriteString("\n")
|
||||||
|
if !strings.HasSuffix(help.String(), "::") {
|
||||||
|
help.WriteString("\n")
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_code_block_line := func(i int, line string) int {
|
||||||
|
if line == "" {
|
||||||
|
help.WriteString("\n")
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
current_indent := indent_of_line(line)
|
||||||
|
if current_indent == 0 {
|
||||||
|
in_code_block = false
|
||||||
|
return handle_non_empty_line(i, line)
|
||||||
|
}
|
||||||
|
line = line[4:]
|
||||||
|
is_prompt := strings.HasPrefix(line, "$ ")
|
||||||
|
if is_prompt {
|
||||||
|
help.WriteString(":yellow:`$ `")
|
||||||
|
line = line[2:]
|
||||||
|
}
|
||||||
|
help.WriteString(line)
|
||||||
|
help.WriteString("\n")
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(lines); i++ {
|
||||||
|
line := lines[i]
|
||||||
|
if in_code_block {
|
||||||
|
i = handle_code_block_line(i, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if line != "" {
|
||||||
|
i = handle_non_empty_line(i, line)
|
||||||
|
} else {
|
||||||
|
i = handle_empty_line(i, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return escape_text_for_man(help.String())
|
||||||
|
}
|
||||||
|
|
||||||
func prepare_help_text_for_display(raw string) string {
|
func prepare_help_text_for_display(raw string) string {
|
||||||
help := strings.Builder{}
|
help := strings.Builder{}
|
||||||
help.Grow(len(raw) + 256)
|
help.Grow(len(raw) + 256)
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
root := cli.NewRootCommand()
|
root := cli.NewRootCommand()
|
||||||
root.ShortDescription = "Fast, statically compiled implementations for various kittens (command line tools for use with kitty)"
|
root.ShortDescription = "Fast, statically compiled implementations of various kittens (command line tools for use with kitty)"
|
||||||
|
root.HelpText = "kitten serves as a launcher for running individual kittens. Each kitten can be run as :code:`kitten command`. The list of available kittens is given below."
|
||||||
root.Usage = "command [command options] [command args]"
|
root.Usage = "command [command options] [command args]"
|
||||||
root.Run = func(cmd *cli.Command, args []string) (int, error) {
|
root.Run = func(cmd *cli.Command, args []string) (int, error) {
|
||||||
cmd.ShowHelp()
|
cmd.ShowHelp()
|
||||||
|
|||||||
@@ -96,4 +96,29 @@ func KittyToolEntryPoints(root *cli.Command) {
|
|||||||
return confirm_and_run_shebang(args)
|
return confirm_and_run_shebang(args)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
// __generate_man_pages__
|
||||||
|
root.AddSubCommand(&cli.Command{
|
||||||
|
Name: "__generate_man_pages__",
|
||||||
|
Hidden: true,
|
||||||
|
OnlyArgsAllowed: true,
|
||||||
|
Run: func(cmd *cli.Command, args []string) (rc int, err error) {
|
||||||
|
q := root
|
||||||
|
if len(args) > 0 {
|
||||||
|
for _, scname := range args {
|
||||||
|
sc := q.FindSubCommand(scname)
|
||||||
|
if sc == nil {
|
||||||
|
return 1, fmt.Errorf("No sub command named: %s found", scname)
|
||||||
|
}
|
||||||
|
if err = sc.GenerateManPages(1, true); err != nil {
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = q.GenerateManPages(1, false); err != nil {
|
||||||
|
rc = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user