Files
obsidian-metadata/src/obsidian_metadata/cli.py
Nathaniel Landau c5766af678 fix: add custom exceptions (#29)
* feat: add custom exceptions to metadata creation

* refactor: utility function for finding inline metadata in content

* fix: use InlineTagError for exceptions parsing inline tags

* fix: improve error messages

* build(deps): bump dependencies

* fix: use BadParameter exception when appropriate
2023-03-29 13:31:23 -04:00

173 lines
5.6 KiB
Python

"""obsidian-metadata CLI."""
from pathlib import Path
from typing import Optional
import questionary
import typer
from obsidian_metadata._config import Config
from obsidian_metadata._utils import (
alerts,
clear_screen,
docstring_parameter,
version_callback,
)
from obsidian_metadata._utils.console import console
from obsidian_metadata.models import Application
app = typer.Typer(add_completion=False, no_args_is_help=True, rich_markup_mode="rich")
typer.rich_utils.STYLE_HELPTEXT = ""
HELP_TEXT = """
"""
@app.command()
@docstring_parameter(__package__)
def main(
config_file: Path = typer.Option(
Path(Path.home() / f".{__package__}.toml"),
help="Specify a custom path to a configuration file",
show_default=False,
),
export_csv: Path = typer.Option(
None,
help="Exports all metadata to a specified CSV file and exits.",
show_default=False,
dir_okay=False,
file_okay=True,
),
export_json: Path = typer.Option(
None,
help="Exports all metadata to a specified JSON file and exits.",
show_default=False,
dir_okay=False,
file_okay=True,
),
export_template: Path = typer.Option(
None,
help="Exports all notes and their metadata to a specified CSV file and exits. Use to create a template for batch updates.",
show_default=False,
dir_okay=False,
file_okay=True,
),
import_csv: Path = typer.Option(
None,
help="Import a CSV file with bulk updates to metadata.",
show_default=False,
dir_okay=False,
file_okay=True,
),
vault_path: Path = typer.Option(
None,
help="Path to Obsidian vault",
show_default=False,
),
dry_run: bool = typer.Option(
False,
"--dry-run",
"-n",
help="Dry run - don't actually change anything",
),
log_file: Path = typer.Option(
Path(Path.home() / "logs" / "obsidian_metadata.log"),
help="Path to log file",
show_default=True,
dir_okay=False,
file_okay=True,
exists=False,
),
log_to_file: bool = typer.Option(
False,
"--log-to-file",
help="Log to file",
show_default=True,
),
verbosity: int = typer.Option(
0,
"-v",
"--verbose",
show_default=False,
help="""Set verbosity level (0=WARN, 1=INFO, 2=DEBUG, 3=TRACE)""",
count=True,
),
version: Optional[bool] = typer.Option( # noqa: ARG001
None, "--version", help="Print version and exit", callback=version_callback, is_eager=True
),
) -> None:
r"""Make batch updates to metadata in an Obsidian vault. No changes are made to the Vault until they are explicitly committed.
[bold] [/]
[bold underline]It is strongly recommended that you back up your vault prior to committing changes.[/] This script makes changes directly to the markdown files in your vault. Once the changes are committed, there is no ability to recreate the original information unless you have a backup. Follow the instructions in the script to create a backup of your vault if needed. The author of this script is not responsible for any data loss that may occur. Use at your own risk.
[bold underline]Configuration:[/]
Configuration is specified in a configuration file. On First run, this file will be created at [tan]~/.{0}.env[/]. Any options specified on the command line will override the configuration file.
Full usage information is available at https://github.com/natelandau/obsidian-metadata
"""
# Instantiate logger
alerts.LoggerManager( # pragma: no cover
log_file,
verbosity,
log_to_file,
)
banner = r"""
___ _ _ _ _
/ _ \| |__ ___(_) __| (_) __ _ _ __
| | | | '_ \/ __| |/ _` | |/ _` | '_ \
| |_| | |_) \__ \ | (_| | | (_| | | | |
\___/|_.__/|___/_|\__,_|_|\__,_|_| |_|
| \/ | ___| |_ __ _ __| | __ _| |_ __ _
| |\/| |/ _ \ __/ _` |/ _` |/ _` | __/ _` |
| | | | __/ || (_| | (_| | (_| | || (_| |
|_| |_|\___|\__\__,_|\__,_|\__,_|\__\__,_|
"""
clear_screen()
console.print(banner)
config: Config = Config(config_path=config_file, vault_path=vault_path)
if len(config.vaults) == 0:
typer.echo("No vaults configured. Exiting.")
raise typer.BadParameter("No vaults configured. Exiting.")
if len(config.vaults) == 1:
application = Application(dry_run=dry_run, config=config.vaults[0])
else:
vault_names = [vault.name for vault in config.vaults]
vault_name = questionary.select(
"Select a vault to process:",
choices=vault_names,
).ask()
if vault_name is None:
raise typer.Exit(code=1)
vault_to_use = next(vault for vault in config.vaults if vault.name == vault_name)
application = Application(dry_run=dry_run, config=vault_to_use)
if export_json is not None:
path = Path(export_json).expanduser().resolve()
application.noninteractive_export_json(path)
raise typer.Exit(code=0)
if export_csv is not None:
path = Path(export_json).expanduser().resolve()
application.noninteractive_export_csv(path)
raise typer.Exit(code=0)
if export_template is not None:
path = Path(export_template).expanduser().resolve()
application.noninteractive_export_template(path)
raise typer.Exit(code=0)
if import_csv is not None:
path = Path(import_csv).expanduser().resolve()
application.noninteractive_bulk_import(path)
raise typer.Exit(code=0)
application.application_main()
if __name__ == "__main__":
app()