feat: add --export-template cli option

This commit is contained in:
Nathaniel Landau
2023-03-21 17:40:13 -04:00
parent 4e053bda29
commit 08999cb055
6 changed files with 61 additions and 25 deletions

View File

@@ -27,6 +27,7 @@ pip install obsidian-metadata
- `--dry-run`: Make no destructive changes
- `--export-csv`: Specify a path and create a CSV export of all metadata
- `--export-json`: Specify a path and create a JSON export of all metadata
- `--export-template`: Specify a path and export all notes with their associated metadata to a CSV file for use as a bulk import template
- `--help`: Shows interactive help and exits
- `--log-file`: Specify a log file location
- `--log-to-file`: Will log to a file
@@ -64,7 +65,7 @@ Once installed, run `obsidian-metadata` in your terminal to enter an interactive
- **List and clear filters**: List all current filters and clear one or all
- **List notes in scope**: List notes that will be processed.
**Bulk Edit Metadata** from a CSV file (See the _making bulk edits_ section below)
**Bulk Edit Metadata** from a CSV file (See the _[Make Bulk Updates](https://github.com/natelandau/obsidian-metadata#make-bulk-updates)_ section below)
**Add Metadata**: Add new metadata to your vault.
@@ -139,7 +140,7 @@ Below is an example with two vaults.
To bypass the configuration file and specify a vault to use at runtime use the `--vault-path` option.
### Making bulk edits
### Make Bulk Updates
Bulk edits are supported by importing a CSV file containing the following columns. Column headers must be lowercase.
@@ -167,7 +168,10 @@ How bulk imports work:
- **Existing metadata in a matching note will be rewritten**. This may result in it's location and/or formatting within the note being changed
- Inline tags ignore any value added to the `key` column
You can export all your notes with their associated metadata in this format from the "Export Metadata" section of the script to be used as a template for your bulk changes.
Create a CSV template for making bulk updates containing all your notes and their associated metadata by
1. Using the `--export-template` cli command; or
2. Selecting the `Metadata by note` option within the `Export Metadata` section of the app
# Contributing

28
poetry.lock generated
View File

@@ -4,7 +4,7 @@
name = "argcomplete"
version = "2.0.6"
description = "Bash tab completion for argparse"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -100,7 +100,7 @@ files = [
name = "charset-normalizer"
version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6.0"
files = [
@@ -142,7 +142,7 @@ files = [
name = "commitizen"
version = "2.42.1"
description = "Python commitizen client tool"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6.2,<4.0.0"
files = [
@@ -231,7 +231,7 @@ toml = ["tomli"]
name = "decli"
version = "0.5.2"
description = "Minimal, easy-to-use, declarative cli tool"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -354,7 +354,7 @@ tests = ["pytest", "pytest-cov", "pytest-mock"]
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
category = "main"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -416,7 +416,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
name = "markupsafe"
version = "2.1.2"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -562,7 +562,7 @@ setuptools = "*"
name = "packaging"
version = "23.0"
description = "Core utilities for Python packages"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -596,14 +596,14 @@ files = [
[[package]]
name = "pdoc"
version = "13.0.0"
version = "13.0.1"
description = "API Documentation for Python Projects"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pdoc-13.0.0-py3-none-any.whl", hash = "sha256:f9088b1c10f3296f46a08796e05e307470af5f4253f71d536781f6c305baf912"},
{file = "pdoc-13.0.0.tar.gz", hash = "sha256:aadbf6c757c6e65c4754d6c26c4eb6c1bf8c7a9fb893f1fbe5a7b879dde59e46"},
{file = "pdoc-13.0.1-py3-none-any.whl", hash = "sha256:16a24914280ed318896ad798674e2b0d11832297fdea95632fa472e3d171e247"},
{file = "pdoc-13.0.1.tar.gz", hash = "sha256:4d84056847728203b8789ca8a8d0c8003f25002b3caef3365f6f21a1e4228a1b"},
]
[package.dependencies]
@@ -839,7 +839,7 @@ testing = ["filelock"]
name = "pyyaml"
version = "6.0"
description = "YAML parser and emitter for Python"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -1172,7 +1172,7 @@ widechars = ["wcwidth"]
name = "termcolor"
version = "2.2.0"
description = "ANSI color formatting for output in terminal"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -1275,7 +1275,7 @@ files = [
name = "typing-extensions"
version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -1349,4 +1349,4 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "e9e2ff35a5ae15991d1d123dffa9f15fdf5afaf00624c26577412555d0464eaf"
content-hash = "8fa62f96cc77eac773497573dcbdd5666173cbec56374fea73a814f3fb7f5338"

View File

@@ -26,7 +26,6 @@
shellingham = "^1.5.0.post1"
tomlkit = "^0.11.6"
typer = "^0.7.0"
commitizen = "^2.42.1"
[tool.poetry.group.test.dependencies]
pytest = "^7.2.2"
@@ -41,14 +40,14 @@
coverage = "^7.2.2"
interrogate = "^1.5.0"
mypy = "^1.1.1"
pdoc = "^13.0.0"
pdoc = "^13.0.1"
poethepoet = "^0.18.1"
pre-commit = "^3.2.0"
ruff = "0.0.257"
ruff = "^0.0.257"
sh = "2.0.3"
typeguard = "^3.0.1"
types-python-dateutil = "^2.8.19.10"
vulture = "^2.7"
sh = "2.0.3"
[tool.black]
line-length = 100

View File

@@ -143,7 +143,7 @@ for group in groups:
notice(
f"Updating {p} from {packages[p]['current_version']} to {packages[p]['new_version']}"
)
sh.poetry("add", f"{p}@{packages[p]['new_version']}", "--group", group, _fg=True)
sh.poetry("add", f"{p}@latest", "--group", group, _fg=True)
sh.poetry("update", _fg=True)
success("All dependencies are up to date")

View File

@@ -34,14 +34,21 @@ def main(
),
export_csv: Path = typer.Option(
None,
help="Exports all metadata to a specified CSV file and exits. (Will overwrite any existing file)",
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. (Will overwrite any existing file)",
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,
@@ -142,6 +149,10 @@ def main(
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_csv(path)
raise typer.Exit(code=0)
application.application_main()

View File

@@ -17,7 +17,7 @@ def test_version() -> None:
"""Test printing version and then exiting."""
result = runner.invoke(app, ["--version"])
assert result.exit_code == 0
assert result.output == Regex(r"obsidian_metadata: v\d+\.\d+\.\d+$")
assert "obsidian_metadata: v" in result.output
def test_application(tmp_path) -> None:
@@ -51,3 +51,25 @@ def test_application(tmp_path) -> None:
assert banner in result.output
assert result.exit_code == 1
def test_export_template(tmp_path) -> None:
"""Test the export template command."""
source_dir = Path(__file__).parent / "fixtures" / "test_vault"
dest_dir = Path(tmp_path / "vault")
if not source_dir.exists():
raise FileNotFoundError(f"Sample vault not found: {source_dir}")
shutil.copytree(source_dir, dest_dir)
config_path = tmp_path / "config.toml"
export_path = tmp_path / "export_template.csv"
result = runner.invoke(
app,
["--vault-path", dest_dir, "--config-file", config_path, "--export-template", export_path],
)
assert "SUCCESS | Exported metadata to" in result.output
assert result.exit_code == 0
assert export_path.exists()