Merge pull request #657 from karlch/improve-docs

Improve docs
This commit is contained in:
Christian Karl 2023-07-15 11:06:41 +02:00 committed by GitHub
commit f04a64feef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 221 additions and 93 deletions

View File

@ -0,0 +1,45 @@
Command Line Arguments
======================
When starting vimiv form the command line you have the ability to pass a number of
different argument to vimiv.
Examples
--------
In the following we present a few use cases of command line arguments.
* Start in library view with the thumbnail grid displayed::
vimiv * --command "enter thumbnail" --command "enter library"
* Start in read-only mode. This prevents accidental modification (renaming, moving, editing etc.) of any images::
vimiv --set read_only true
* Change WM_CLASS_INSTANCE to identify a vimiv instance::
vimiv --qt-args "--name myVimivInstance"
* Print the last selected image to STDOUT when quitting::
vimiv --output "%"
* Use vimiv as *Rofi for Images* to make a selection from candidate images::
mySel=$(echo $myCand | vimiv --input --output "%m" --command "enter thumbnail")
* Print debug logs of your amazing plugin you are writing and of the `api._mark` module which does not behave as you are expecting::
vimiv --debug myAmazingPlugin api._mark
Command Line Arguments
----------------------
The general calling structure is:
.. include:: synopsis.rstsrc
The following is an exhaustive list of all available arguments:
.. include:: options.rstsrc

View File

@ -10,6 +10,7 @@ The following documents are available:
getting_started
commands
configuration/index
cl_options/index
metadata
contributing
migrating

View File

@ -27,12 +27,12 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "VIMIV" "1" "Dec 26, 2021" "" "vimiv"
.TH "VIMIV" "1" "Jan 08, 2022" "" "vimiv"
.SH NAME
vimiv \- an image viewer with vim-like keybindings
.SH SYNOPSIS
.sp
\fBvimiv\fP [\fBPATH\fP] [\fB\-h\fP] [\fB\-\-help\fP] [\fB\-f\fP] [\fB\-\-fullscreen\fP] [\fB\-v\fP] [\fB\-\-version\fP] [\fB\-g\fP \fIWIDTHxHEIGHT\fP] [\fB\-\-geometry\fP \fIWIDTHxHEIGHT\fP] [\fB\-\-temp\-basedir\fP] [\fB\-\-config\fP \fIFILE\fP] [\fB\-\-keyfile\fP \fIFILE\fP] [\fB\-s\fP \fIOPTION\fP \fIVALUE\fP] [\fB\-\-set\fP \fIOPTION\fP \fIVALUE\fP] [\fB\-\-log\-level\fP \fILEVEL\fP] [\fB\-\-command\fP \fICOMMAND\fP] [\fB\-b\fP \fIDIRECTORY\fP] [\fB\-\-basedir\fP \fIDIRECTORY\fP] [\fB\-o\fP \fITEXT\fP] [\fB\-\-output\fP \fITEXT\fP] [\fB\-i\fP] [\fB\-\-input\fP] [\fB\-\-debug\fP \fIMODULE\fP] [\fB\-\-qt\-args\fP \fIARGS\fP]
\fBvimiv\fP [\fBPATH\fP] [\fB\-\-help\fP|\fB\-h\fP] [\fB\-\-fullscreen\fP|\fB\-f\fP] [\fB\-\-version\fP|\fB\-v\fP] [\fB\-\-geometry\fP \fIWIDTHxHEIGHT\fP|\fB\-g\fP \fIWIDTHxHEIGHT\fP] [\fB\-\-temp\-basedir\fP] [\fB\-\-config\fP \fIFILE\fP] [\fB\-\-keyfile\fP \fIFILE\fP] [\fB\-\-set\fP \fIOPTION\fP \fIVALUE\fP|\fB\-s\fP \fIOPTION\fP \fIVALUE\fP] [\fB\-\-log\-level\fP \fILEVEL\fP] [\fB\-\-command\fP \fICOMMAND\fP] [\fB\-\-basedir\fP \fIDIRECTORY\fP|\fB\-b\fP \fIDIRECTORY\fP] [\fB\-\-output\fP \fITEXT\fP|\fB\-o\fP \fITEXT\fP] [\fB\-\-input\fP|\fB\-i\fP] [\fB\-\-debug\fP \fIMODULE\fP] [\fB\-\-qt\-args\fP \fIARGS\fP]
.SH DESCRIPTION
.sp
Vimiv is an image viewer with vim\-like keybindings. It is written in python3
@ -54,7 +54,7 @@ Complete customization with style sheets
A much more complete documentation can be found under
\fI\%https://karlch.github.io/vimiv\-qt/\fP\&.
.SH OPTIONS
.SS positional arguments
.SS POSITIONAL ARGUMENTS
.sp
\fBPATH\fP
.INDENT 0.0
@ -62,30 +62,30 @@ A much more complete documentation can be found under
Paths to open
.UNINDENT
.UNINDENT
.SS optional arguments
.SS OPTIONS
.sp
\fB\-h\fP, \fB\-\-help\fP
\fB\-\-help\fP, \fB\-h\fP
.INDENT 0.0
.INDENT 3.5
show this help message and exit
.UNINDENT
.UNINDENT
.sp
\fB\-f\fP, \fB\-\-fullscreen\fP
\fB\-\-fullscreen\fP, \fB\-f\fP
.INDENT 0.0
.INDENT 3.5
Start fullscreen
.UNINDENT
.UNINDENT
.sp
\fB\-v\fP, \fB\-\-version\fP
\fB\-\-version\fP, \fB\-v\fP
.INDENT 0.0
.INDENT 3.5
Print version information and exit
.UNINDENT
.UNINDENT
.sp
\fB\-g\fP \fIWIDTHxHEIGHT\fP, \fB\-\-geometry\fP \fIWIDTHxHEIGHT\fP
\fB\-\-geometry\fP \fIWIDTHxHEIGHT\fP, \fB\-g\fP \fIWIDTHxHEIGHT\fP
.INDENT 0.0
.INDENT 3.5
Set the starting geometry
@ -113,7 +113,7 @@ Use FILE as keybinding file
.UNINDENT
.UNINDENT
.sp
\fB\-s\fP \fIOPTION\fP \fIVALUE\fP, \fB\-\-set\fP \fIOPTION\fP \fIVALUE\fP
\fB\-\-set\fP \fIOPTION\fP \fIVALUE\fP, \fB\-s\fP \fIOPTION\fP \fIVALUE\fP
.INDENT 0.0
.INDENT 3.5
Set a temporary setting
@ -134,27 +134,27 @@ Run COMMAND on startup, usable multiple times
.UNINDENT
.UNINDENT
.sp
\fB\-b\fP \fIDIRECTORY\fP, \fB\-\-basedir\fP \fIDIRECTORY\fP
\fB\-\-basedir\fP \fIDIRECTORY\fP, \fB\-b\fP \fIDIRECTORY\fP
.INDENT 0.0
.INDENT 3.5
Directory to use for all storage
.UNINDENT
.UNINDENT
.sp
\fB\-o\fP \fITEXT\fP, \fB\-\-output\fP \fITEXT\fP
\fB\-\-output\fP \fITEXT\fP, \fB\-o\fP \fITEXT\fP
.INDENT 0.0
.INDENT 3.5
Wildcard expanded string to print to standard output upon quit
.UNINDENT
.UNINDENT
.sp
\fB\-i\fP, \fB\-\-input\fP
\fB\-\-input\fP, \fB\-i\fP
.INDENT 0.0
.INDENT 3.5
Read paths to open from standard input
.UNINDENT
.UNINDENT
.SS development arguments
.SS DEVELOPMENT ARGUMENTS
.sp
\fB\-\-debug\fP \fIMODULE\fP
.INDENT 0.0
@ -175,6 +175,6 @@ Arguments to pass to qt directly, use quotes to pass multiple arguments
.SH AUTHOR
Christian Karl
.SH COPYRIGHT
2017-2021, Christian Karl
2017-2022, Christian Karl
.\" Generated by docutils manpage writer.
.

View File

@ -50,17 +50,24 @@ class RSTFile:
title: Title of the table.
"""
# Find out size of first column
length = max([len(elem[0]) for elem in rows])
ncols = len(rows[0])
lengths = [max([len(elem[i]) for elem in rows]) for i in range(ncols)]
empty_row = ["=" * n for n in lengths]
def format_row(row=empty_row):
parts = " ".join(f"{elem:{n}s}" for elem, n in zip(row, lengths))
return f" {parts}\n"
# Header
self.write(".. table:: %s\n :widths: %s\n\n" % (title, widths))
self.write(" %s ===========\n" % (length * "="))
self.write(" %-*s %s\n" % (length, rows[0][0], rows[0][1]))
self.write(" %s ===========\n" % (length * "="))
self.write(f".. table:: {title}\n :widths: {widths}\n\n")
self.write(format_row())
self.write(format_row(rows[0]))
self.write(format_row())
# Content
for row in rows[1:]:
self.write(" %-*s %s\n" % (length, row[0], row[1]))
self.write(format_row(row))
# Footer
self.write(" %s ===========\n" % (length * "="))
self.write(format_row())
def _write_header(self):
"""Write header to file explaining that the file was autogenerated."""

View File

@ -6,10 +6,13 @@
"""Generate reST documentation from source code docstrings."""
import collections
import inspect
import importlib
import os
import sys
import textwrap
from typing import Any, Dict, List, NamedTuple
# Startup is imported to create all the commands and keybindings via their decorators
from vimiv import api, parser, startup # pylint: disable=unused-import
@ -65,12 +68,15 @@ def generate_settings():
print("generating settings...")
filename = "docs/documentation/configuration/settings_table.rstsrc"
with RSTFile(filename) as f:
rows = [("Setting", "Description")]
rows = [("Setting", "Description", "Default", "Type")]
for name in sorted(api.settings._storage.keys()):
setting = api.settings._storage[name]
if setting.desc: # Otherwise the setting is meant to be hidden
rows.append((name, setting.desc))
f.write_table(rows, title="Overview of settings", widths="30 70")
default = str(setting.default)
if len(default) > 20: # Cut very long defaults (exif)
default = default[:17] + "..."
rows.append((name, setting.desc, default, setting.typ.__name__))
f.write_table(rows, title="Overview of settings", widths="20 60 15 5")
def generate_keybindings():
@ -93,105 +99,173 @@ def _gen_keybinding_rows(bindings):
def generate_commandline_options():
"""Generate file including the command line options."""
argparser = parser.get_argparser()
groups, titles = _get_options(argparser)
# Synopsis
arguments = _get_arguments(argparser)
# Synopsis Man Page
filename_synopsis = "docs/manpage/synopsis.rstsrc"
with open(filename_synopsis, "w", encoding="utf-8") as f:
synopsis_options = ["[%s]" % (title) for title in titles]
synopsis = "**vimiv** %s" % (" ".join(synopsis_options))
f.write(synopsis)
# Options
f.write(_generate_synopsis(arguments, emph="*", sep=r"\ \|\ "))
# Synopsis Documentation
filename_synopsis = "docs/documentation/cl_options/synopsis.rstsrc"
with open(filename_synopsis, "w", encoding="utf-8") as f:
raw_synopsis_doc = _generate_synopsis(arguments)
synopsis_doc = "\n ".join(textwrap.wrap(raw_synopsis_doc, width=79))
f.write(f".. code-block::\n\n {synopsis_doc}")
# Command Listing
groups = collections.defaultdict(list)
for arg in arguments:
groups[arg.group].append(arg)
# Command Listing Man Page
filename_options = "docs/manpage/options.rstsrc"
with RSTFile(filename_options) as f:
for title, argstr in groups.items():
f.write_section(title)
f.write(argstr)
_generate_cli_man(groups, f)
# Command Listing Documentation
filename_options = "docs/documentation/cl_options/options.rstsrc"
with RSTFile(filename_options) as f:
_generate_cli_doc(groups, f)
def _get_options(argparser):
"""Retrieve the options from the argument parser.
class ParserArgument(NamedTuple):
"""Storage class for a single command line argument."""
Returns:
groups: Dictionary of group titles and argument strings.
titles: List containing all argument titles.
"""
groups = {}
titles = []
for group in argparser._action_groups:
argstr = ""
for action in group._group_actions:
argstr += (
_format_positional(action, titles)
if "positional" in group.title
else _format_optional(action, titles)
)
groups[group.title] = argstr
return groups, titles
group: str
longname: str
shortname: str
metavar: Any
description: str
@property
def is_positional(self):
return self.longname is self.shortname is None
def get_names(self, formatter: str) -> List[str]:
"""Retrieve list of long and shortname which are not None."""
return [
self._format(name, formatter)
for name in [self.longname, self.shortname]
if name is not None
]
def get_metavar(self, formatter: str) -> str:
"""Retrieve metavar."""
if self.metavar is None:
return ""
if isinstance(self.metavar, tuple):
return " ".join(self._format(e, formatter) for e in self.metavar)
return self._format(self.metavar, formatter)
def get_name_metavar(
self, name_formatter: str, metavar_formatter: str
) -> List[str]:
"""Retrieve all not-none names and append metavar."""
names = self.get_names(name_formatter)
metavar = self.get_metavar(metavar_formatter)
lst = names if names else [metavar]
return [f"{e} {metavar}" if metavar else e for e in lst]
def _format(self, element: str, formatter: str) -> str:
"""Wrap the element into the format string."""
return formatter + element + formatter
def _format_optional(action, titles):
"""Format optional argument neatly.
def _get_arguments(argparser: parser.argparse.ArgumentParser) -> List[ParserArgument]:
"""Retrieve all arguments from the passed argparser.
Args:
action: The argparser action.
titles: List of titles to update with the title(s) of this argument.
argparser: Argument parser where the arguments get extracted from.
Returns:
Formatted string of this argument.
List of ParserArgument where each element represents one argument of the parser.
"""
_titles = _format_optional_title(action)
titles.extend(_titles)
title = ", ".join(_titles)
desc = action.help
return _format_option(title, desc)
def argument_from_action(group, action):
if len(action.option_strings) == 2:
shortname, longname = action.option_strings
elif len(action.option_strings) == 1:
longname = action.option_strings[0]
shortname = None
else:
longname = shortname = None
return ParserArgument(
group=group.title,
longname=longname,
shortname=shortname,
metavar=action.metavar,
description=action.help,
)
return [
argument_from_action(group, action)
for group in argparser._action_groups
for action in group._group_actions
]
def _format_positional(action, titles):
"""Format positional argument neatly.
def _generate_synopsis(arguments: List[ParserArgument], emph: str = "", sep="|") -> str:
"""Generate formatted synopsis of vimiv.
Args:
action: The argparser action.
titles: List of titles to update with the title of this argument.
arguments: List of instances of ParserArgument.
emph: Character used for emphasis.
sep: String used to separate different versions of the same argument, e.g. -o
and --output.
Returns:
Formatted string of this argument.
Formatted synopsis for the man page.
"""
title = "**%s**" % (action.metavar)
titles.append(title)
desc = action.help
return _format_option(title, desc)
emph2 = 2 * emph
synopsis = f"{emph2}vimiv{emph2}"
for arg in arguments:
if arg.is_positional:
synopsis += f" [{arg.get_metavar(emph2)}]"
else:
command = sep.join(arg.get_name_metavar(emph2, emph))
synopsis += f" [{command}]"
return synopsis
def _format_option(title, desc):
"""Format an option neatly.
def _generate_cli_man(groups: Dict[str, List[ParserArgument]], f: RSTFile) -> None:
"""Generate commands listing with man page formatting.
Args:
title: The title of this option.
desc: The help description of this option.
groups: List of ParserArgument sorted by their respective group.
f: RSTFile to write to.
"""
return "%s\n\n %s\n\n" % (title, desc)
for group in groups.keys():
arguments = groups[group]
f.write_section(group.upper())
for arg in arguments:
if arg.is_positional:
f.write(f"{arg.get_metavar('**')}\n\n\t{arg.description}\n\n")
else:
f.write(f"{', '.join(arg.get_name_metavar('**', '*'))}\n\n")
f.write(f"\t{arg.description}\n\n")
def _format_optional_title(action):
"""Format the title of an optional argument neatly.
The title depends on the number of arguments this action requires.
def _generate_cli_doc(groups: Dict[str, List[ParserArgument]], f: RSTFile) -> None:
"""Generate commands listing with documentation formatting.
Args:
action: The argparser action.
Returns:
Formatted title string of this argument.
groups: List of ParserArgument sorted by their respective group.
f: RSTFile to write to.
"""
formats = []
for option in action.option_strings:
if isinstance(action.metavar, str): # One argument
title = "**%s** *%s*" % (option, action.metavar)
elif isinstance(action.metavar, tuple): # Multiple arguments
elems = ["*%s*" % (elem) for elem in action.metavar]
title = "**%s** %s" % (option, " ".join(elems))
else: # No arguments
title = "**%s**" % (option)
formats.append(title)
return formats
for group in groups.keys():
arguments = groups[group]
rows = [("Command", "Description")]
for arg in arguments:
if arg.is_positional:
name = arg.get_metavar("``")
else:
name = ", ".join(f"``{e}``" for e in arg.get_name_metavar("", ""))
rows.append((name, arg.description))
f.write_table(rows, title=group.capitalize(), widths="50 50")
def generate_plugins():

View File

@ -78,6 +78,7 @@ deps = {[testenv:docs]deps}
commands =
{toxinidir}/scripts/src2rst.py
sphinx-build -b man docs misc
allowlist_externals = {[testenv:docs]allowlist_externals}
# Settings for check-manifest
[check-manifest]