move from click cli -> pydantic-settings cli
This commit is contained in:
parent
5411e55c17
commit
c0c53dc5fc
|
|
@ -6,8 +6,6 @@ authors = [{ name = "gitmatt", email = "git@publicmatt.com" }]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click>=8.1.8",
|
|
||||||
"click-default-group>=1.2.4",
|
|
||||||
"jinja2>=3.1.5",
|
"jinja2>=3.1.5",
|
||||||
"lxml>=5.3.1",
|
"lxml>=5.3.1",
|
||||||
"markdown>=3.7",
|
"markdown>=3.7",
|
||||||
|
|
@ -18,7 +16,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
plan = "plan:cli.cli"
|
plan = "plan:__main__.cli"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
||||||
|
|
||||||
from .config import Config
|
|
||||||
|
|
||||||
config = Config()
|
|
||||||
|
|
||||||
|
|
||||||
def get_template(template_name: str = "plan.html", plan_path: Path | None = None):
|
|
||||||
""" """
|
|
||||||
templates = Environment(
|
|
||||||
loader=PackageLoader("plan"), autoescape=select_autoescape()
|
|
||||||
)
|
|
||||||
template = templates.get_template(template_name)
|
|
||||||
today = datetime.today().strftime("%Y-%m-%d")
|
|
||||||
return template.render(today=today, plan_path=plan_path)
|
|
||||||
|
|
||||||
|
|
||||||
def plan_path() -> Path:
|
|
||||||
"""
|
|
||||||
returns: Path to current date's .plan.md
|
|
||||||
"""
|
|
||||||
today = datetime.today().strftime("%Y-%m-%d")
|
|
||||||
path = config.app_path / f"{today}.md"
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def print_plan(plan_path: Path):
|
|
||||||
"""
|
|
||||||
send .plan.md to printer.
|
|
||||||
|
|
||||||
uses pandoc and lp shell tools.
|
|
||||||
"""
|
|
||||||
# preamble ="""
|
|
||||||
# ---
|
|
||||||
# documentclass: extarticle
|
|
||||||
# fontsize: 20pt
|
|
||||||
# ---
|
|
||||||
# """
|
|
||||||
cat_process = subprocess.Popen(["cat", str(plan_path)], stdout=subprocess.PIPE)
|
|
||||||
pandoc_process = subprocess.Popen(
|
|
||||||
["pandoc", "-t", "pdf", "-V", "geometry:margin=.5in", "-"],
|
|
||||||
stdin=cat_process.stdout,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
["lp", "-o", "sides=two-sided-long-edge", "-o", "number-up=2"],
|
|
||||||
stdin=pandoc_process.stdout,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_and_open_template(plan: Path, name: str = "plan.html") -> int:
|
|
||||||
plan.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
plan.touch()
|
|
||||||
|
|
||||||
template = get_template(name, plan_path=plan)
|
|
||||||
with plan.open("w") as f:
|
|
||||||
f.write(template)
|
|
||||||
|
|
||||||
editor = os.getenv("EDITOR", "nvim")
|
|
||||||
os.chdir(plan.parent)
|
|
||||||
p = subprocess.Popen([editor, str(plan)])
|
|
||||||
sys.exit(p.wait())
|
|
||||||
|
|
||||||
|
|
||||||
def open_in_editor(plan: Path):
|
|
||||||
plan.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
if not plan.exists():
|
|
||||||
plan.touch()
|
|
||||||
with plan.open("w") as f:
|
|
||||||
f.write(default_template(plan_path=plan))
|
|
||||||
editor = os.getenv("EDITOR", "nvim")
|
|
||||||
os.chdir(Path().home() / ".plan")
|
|
||||||
subprocess.run([editor, str(plan)])
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
from pydantic_settings import (
|
||||||
|
CliApp,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .cli import PlanCli
|
||||||
|
|
||||||
|
|
||||||
|
def cli():
|
||||||
|
app = CliApp()
|
||||||
|
app.run(PlanCli)
|
||||||
|
|
@ -1,54 +1,20 @@
|
||||||
import sys
|
from pydantic import Field
|
||||||
from pathlib import Path
|
from pydantic_settings import (
|
||||||
|
BaseSettings,
|
||||||
import click
|
CliApp,
|
||||||
from click_default_group import DefaultGroup
|
CliSubCommand,
|
||||||
|
get_subcommand,
|
||||||
from .main import (
|
|
||||||
create_plan,
|
|
||||||
edit_plan,
|
|
||||||
plan_path,
|
|
||||||
print_plan,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .commands import main
|
||||||
@click.group(cls=DefaultGroup, default="main", default_if_no_args=True)
|
|
||||||
def cli():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("print")
|
class PlanCli(BaseSettings):
|
||||||
@click.option(
|
main_: CliSubCommand[main.Main] = Field(alias="main")
|
||||||
"--plan_path",
|
print_: CliSubCommand[main.Print] = Field(alias="print")
|
||||||
"-p",
|
|
||||||
"plan_path",
|
|
||||||
type=click.Path(exists=True, dir_okay=False, readable=True, path_type=Path),
|
|
||||||
default=plan_path(),
|
|
||||||
)
|
|
||||||
def send_to_printer(plan_path: Path):
|
|
||||||
print_plan(plan_path)
|
|
||||||
|
|
||||||
|
def cli_cmd(self) -> None:
|
||||||
@cli.command("main")
|
if get_subcommand(self, is_required=False) is None:
|
||||||
@click.argument("template_name", required=True, default="plan.html")
|
CliApp.run(main.Main)
|
||||||
@click.option(
|
else:
|
||||||
"--print/--no-print",
|
CliApp.run_subcommand(self)
|
||||||
"is_print",
|
|
||||||
is_flag=True,
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
@click.option("--quiet", "quiet", type=bool, is_flag=True, default=True)
|
|
||||||
def open_today(template_name, is_print: bool = False, quiet: bool = True):
|
|
||||||
"""
|
|
||||||
open today
|
|
||||||
|
|
||||||
plan {:template|plan.html} {--quiet} {--print}
|
|
||||||
|
|
||||||
:template can be one of: {plan|work}.html
|
|
||||||
"""
|
|
||||||
path = plan_path()
|
|
||||||
content = create_plan(plan_path=path, name=template_name)
|
|
||||||
if is_print:
|
|
||||||
print(content)
|
|
||||||
else:
|
|
||||||
sys.exit(edit_plan(path))
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
from pydantic_settings import (
|
||||||
|
BaseSettings,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Base(BaseSettings):
|
||||||
|
model_config = {"env_file": ".env", "env_prefix": "PLAN_", "extra": "ignore"}
|
||||||
|
base_path: Path = Field(
|
||||||
|
default=Path().home() / ".plan", description="base path for .plan files"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
|
from pydantic import Field, computed_field
|
||||||
|
from pydantic_settings import (
|
||||||
|
CliImplicitFlag,
|
||||||
|
CliPositionalArg,
|
||||||
|
)
|
||||||
|
|
||||||
|
from plan.commands import Base
|
||||||
|
from plan.repository import PlanRepository
|
||||||
|
|
||||||
|
|
||||||
|
class Main(Base):
|
||||||
|
"""
|
||||||
|
open today's plan
|
||||||
|
|
||||||
|
:template can be one of: {plan|work}
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_name: CliPositionalArg[str] = Field(default="plan")
|
||||||
|
|
||||||
|
@computed_field()
|
||||||
|
@property
|
||||||
|
def template(self) -> str:
|
||||||
|
return f"{self.template_name}.md"
|
||||||
|
|
||||||
|
print: CliImplicitFlag[bool] = Field(default=False)
|
||||||
|
|
||||||
|
def create(self, force=False) -> str:
|
||||||
|
plan_path = PlanRepository.path_for_date(base_path=self.base_path)
|
||||||
|
plan_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if not plan_path.exists() or force:
|
||||||
|
content = PlanRepository.get_template(self.template)
|
||||||
|
with plan_path.open("w") as f:
|
||||||
|
f.write(content)
|
||||||
|
else:
|
||||||
|
content = self.get(plan_path)
|
||||||
|
return content
|
||||||
|
|
||||||
|
def get(self, plan_path: Path) -> str:
|
||||||
|
with plan_path.open("r") as f:
|
||||||
|
content = f.read()
|
||||||
|
return content
|
||||||
|
|
||||||
|
def edit(self) -> int:
|
||||||
|
plan_path = PlanRepository.path_for_date(base_path=self.base_path)
|
||||||
|
editor = os.getenv("EDITOR", "nvim")
|
||||||
|
os.chdir(plan_path.parent)
|
||||||
|
p = subprocess.Popen([editor, str(plan_path)])
|
||||||
|
return p.wait()
|
||||||
|
|
||||||
|
def cli_cmd(self) -> None:
|
||||||
|
content = self.create()
|
||||||
|
if self.print:
|
||||||
|
print(content)
|
||||||
|
else:
|
||||||
|
sys.exit(self.edit())
|
||||||
|
|
||||||
|
|
||||||
|
class Print(Base):
|
||||||
|
PREAMBLE: ClassVar[str] = """
|
||||||
|
---
|
||||||
|
documentclass: extarticle
|
||||||
|
fontsize: 20pt
|
||||||
|
---
|
||||||
|
"""
|
||||||
|
|
||||||
|
def print_plan(self, plan_path: Path):
|
||||||
|
"""
|
||||||
|
send .plan.md to printer.
|
||||||
|
|
||||||
|
uses pandoc and lp shell tools.
|
||||||
|
"""
|
||||||
|
preamble = subprocess.Popen(
|
||||||
|
["echo", "-n", self.PREAMBLE], stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
cat = subprocess.Popen(
|
||||||
|
["cat", "-", str(plan_path)],
|
||||||
|
stdin=preamble.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
pandoc = subprocess.Popen(
|
||||||
|
["pandoc", "-t", "pdf", "-V", "geometry:margin=.5in", "-"],
|
||||||
|
stdin=cat.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
send = subprocess.Popen(
|
||||||
|
["lp", "-o", "sides=two-sided-long-edge", "-o", "number-up=2"],
|
||||||
|
stdin=pandoc.stdout,
|
||||||
|
)
|
||||||
|
sys.exit(send.wait())
|
||||||
|
|
||||||
|
def plan_path(self, date: datetime | None = None) -> Path:
|
||||||
|
"""
|
||||||
|
returns: Path to current date's .plan.md
|
||||||
|
"""
|
||||||
|
if date is None:
|
||||||
|
date = datetime.today()
|
||||||
|
today = date.strftime("%Y-%m-%d")
|
||||||
|
path = self.base_path / f"{today}.md"
|
||||||
|
return path
|
||||||
|
|
||||||
|
def cli_cmd(self):
|
||||||
|
path = self.plan_path()
|
||||||
|
self.print_plan(path)
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from pydantic import Field
|
|
||||||
from pydantic_settings import BaseSettings
|
|
||||||
|
|
||||||
|
|
||||||
def default_app_path():
|
|
||||||
path = Path().home() / ".plan"
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseSettings):
|
|
||||||
model_config = {"env_file": ".env", "env_prefix": "PLAN_", "extra": "ignore"}
|
|
||||||
app_path: Path = Field(default_factory=default_app_path)
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from .config import Config
|
|
||||||
|
|
||||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
||||||
|
|
||||||
config = Config()
|
|
||||||
|
|
||||||
|
|
||||||
def get_template(template_name: str = "plan.html", plan_path: Path | None = None):
|
|
||||||
""" """
|
|
||||||
templates = Environment(
|
|
||||||
loader=PackageLoader("plan"), autoescape=select_autoescape()
|
|
||||||
)
|
|
||||||
template = templates.get_template(template_name)
|
|
||||||
today = datetime.today().strftime("%Y-%m-%d")
|
|
||||||
return template.render(today=today, plan_path=plan_path)
|
|
||||||
|
|
||||||
|
|
||||||
def plan_path(date: datetime | None = None) -> Path:
|
|
||||||
"""
|
|
||||||
returns: Path to current date's .plan.md
|
|
||||||
"""
|
|
||||||
if date is None:
|
|
||||||
date = datetime.today()
|
|
||||||
today = date.strftime("%Y-%m-%d")
|
|
||||||
path = config.app_path / f"{today}.md"
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def print_plan(plan_path: Path):
|
|
||||||
"""
|
|
||||||
send .plan.md to printer.
|
|
||||||
|
|
||||||
uses pandoc and lp shell tools.
|
|
||||||
"""
|
|
||||||
preamble = """
|
|
||||||
---
|
|
||||||
documentclass: extarticle
|
|
||||||
fontsize: 20pt
|
|
||||||
---
|
|
||||||
|
|
||||||
"""
|
|
||||||
preamble = subprocess.Popen(["echo", "-n", preamble], stdout=subprocess.PIPE)
|
|
||||||
cat = subprocess.Popen(
|
|
||||||
["cat", "-", str(plan_path)],
|
|
||||||
stdin=preamble.stdout,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
pandoc = subprocess.Popen(
|
|
||||||
["pandoc", "-t", "pdf", "-V", "geometry:margin=.5in", "-"],
|
|
||||||
stdin=cat.stdout,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
send = subprocess.Popen(
|
|
||||||
["lp", "-o", "sides=two-sided-long-edge", "-o", "number-up=2"],
|
|
||||||
stdin=pandoc.stdout,
|
|
||||||
)
|
|
||||||
sys.exit(send.wait())
|
|
||||||
|
|
||||||
|
|
||||||
def create_plan(plan_path: Path, name: str = "plan.html", force=False) -> str:
|
|
||||||
plan_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
if not plan_path.exists() or force:
|
|
||||||
content = get_template(name, plan_path=plan_path)
|
|
||||||
with plan_path.open("w") as f:
|
|
||||||
f.write(content)
|
|
||||||
else:
|
|
||||||
content = get_plan(plan_path)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def get_plan(plan_path: Path) -> str:
|
|
||||||
with plan_path.open("r") as f:
|
|
||||||
content = f.read()
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def edit_plan(plan_path: Path) -> int:
|
|
||||||
editor = os.getenv("EDITOR", "nvim")
|
|
||||||
os.chdir(plan_path.parent)
|
|
||||||
p = subprocess.Popen([editor, str(plan_path)])
|
|
||||||
return p.wait()
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||||
|
|
||||||
|
|
||||||
|
class PlanRepository:
|
||||||
|
@classmethod
|
||||||
|
def path_for_date(cls, base_path: Path, date: datetime | None = None) -> Path:
|
||||||
|
"""
|
||||||
|
returns: Path to current date's .plan.md
|
||||||
|
"""
|
||||||
|
if date is None:
|
||||||
|
date = datetime.today()
|
||||||
|
today = date.strftime("%Y-%m-%d")
|
||||||
|
path = base_path / f"{today}.md"
|
||||||
|
return path
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_template(cls, template_name: str = "plan.html"):
|
||||||
|
""" """
|
||||||
|
templates = Environment(
|
||||||
|
loader=PackageLoader("plan"), autoescape=select_autoescape()
|
||||||
|
)
|
||||||
|
template = templates.get_template(template_name)
|
||||||
|
today = datetime.today().strftime("%Y-%m-%d")
|
||||||
|
return template.render(today=today)
|
||||||
28
uv.lock
28
uv.lock
|
|
@ -24,30 +24,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 },
|
{ url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "click"
|
|
||||||
version = "8.1.8"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "click-default-group"
|
|
||||||
version = "1.2.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "click" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/ce/edb087fb53de63dad3b36408ca30368f438738098e668b78c87f93cd41df/click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e", size = 3505 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f", size = 4123 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|
@ -215,8 +191,6 @@ name = "plan"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
|
||||||
{ name = "click-default-group" },
|
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
{ name = "lxml" },
|
{ name = "lxml" },
|
||||||
{ name = "markdown" },
|
{ name = "markdown" },
|
||||||
|
|
@ -234,8 +208,6 @@ dev = [
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "click", specifier = ">=8.1.8" },
|
|
||||||
{ name = "click-default-group", specifier = ">=1.2.4" },
|
|
||||||
{ name = "jinja2", specifier = ">=3.1.5" },
|
{ name = "jinja2", specifier = ">=3.1.5" },
|
||||||
{ name = "lxml", specifier = ">=5.3.1" },
|
{ name = "lxml", specifier = ">=5.3.1" },
|
||||||
{ name = "markdown", specifier = ">=3.7" },
|
{ name = "markdown", specifier = ">=3.7" },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue