wpctl 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wpctl/__init__.py ADDED
File without changes
wpctl/api/__init__.py ADDED
File without changes
@@ -0,0 +1,104 @@
1
+ import os
2
+
3
+ import requests
4
+
5
+ from wpctl.utils.custom_logger import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ class WordPressAPIError(Exception):
11
+ """WordPress API 呼び出しエラー。"""
12
+
13
+
14
+ class ApiWordpress:
15
+ """WordPress REST API クライアント。
16
+
17
+ Notes:
18
+ WordPress REST API のエンドポイントは通常 /wp-json/wp/v2/
19
+ """
20
+
21
+ def __init__(self) -> None:
22
+ self._site_url = os.environ["WP_SITE_URL"].rstrip("/")
23
+ self._auth = (os.environ["WP_USER"], os.environ["WP_APP_PASSWORD"])
24
+
25
+ def get_me(self) -> dict:
26
+ """認証情報の疎通確認。
27
+
28
+ Returns:
29
+ 認証ユーザーの情報
30
+
31
+ Raises:
32
+ WordPressAPIError: API呼び出しに失敗した場合
33
+ """
34
+ url = f"{self._site_url}/wp-json/wp/v2/users/me"
35
+ try:
36
+ response = requests.get(url, auth=self._auth)
37
+ response.raise_for_status()
38
+ except requests.exceptions.HTTPError as e:
39
+ logger.error(f"認証に失敗しました: {e}")
40
+ raise WordPressAPIError(f"認証に失敗しました: {e}") from e
41
+ logger.info(
42
+ f"Authentication successful for user: {response.json().get('name')}"
43
+ )
44
+ return response.json()
45
+
46
+ def create_post(
47
+ self,
48
+ title: str,
49
+ content: str,
50
+ status: str = "publish",
51
+ ) -> dict:
52
+ """新しい記事を投稿する。
53
+
54
+ Args:
55
+ title: 記事のタイトル
56
+ content: 記事の内容(HTML)
57
+ status: 記事のステータス('publish' または 'draft')
58
+
59
+ Returns:
60
+ 作成された記事の情報
61
+
62
+ Raises:
63
+ WordPressAPIError: API呼び出しに失敗した場合
64
+ """
65
+ url = f"{self._site_url}/wp-json/wp/v2/posts"
66
+ payload = {"title": title, "content": content, "status": status}
67
+ try:
68
+ response = requests.post(url, json=payload, auth=self._auth)
69
+ response.raise_for_status()
70
+ except requests.exceptions.HTTPError as e:
71
+ logger.error(f"記事の投稿に失敗しました: {e}")
72
+ raise WordPressAPIError(f"記事の投稿に失敗しました: {e}") from e
73
+ logger.info(f"Post created successfully: {response.json().get('link')}")
74
+ return response.json()
75
+
76
+ def update_post(
77
+ self,
78
+ post_id: int,
79
+ title: str,
80
+ content: str,
81
+ ) -> dict:
82
+ """既存の記事を更新する。
83
+
84
+ Args:
85
+ post_id: 更新する記事のID
86
+ title: 更新後のタイトル
87
+ content: 更新後の内容(HTML)
88
+
89
+ Returns:
90
+ 更新された記事の情報
91
+
92
+ Raises:
93
+ WordPressAPIError: API呼び出しに失敗した場合
94
+ """
95
+ url = f"{self._site_url}/wp-json/wp/v2/posts/{post_id}"
96
+ payload = {"title": title, "content": content}
97
+ try:
98
+ response = requests.post(url, json=payload, auth=self._auth)
99
+ response.raise_for_status()
100
+ except requests.exceptions.HTTPError as e:
101
+ logger.error(f"記事の更新に失敗しました: id={post_id}, {e}")
102
+ raise WordPressAPIError(f"記事の更新に失敗しました: {e}") from e
103
+ logger.info(f"Post updated successfully: {response.json().get('link')}")
104
+ return response.json()
File without changes
@@ -0,0 +1,22 @@
1
+ from wpctl.api.api_wordpress import ApiWordpress
2
+ from wpctl.libs.file_reader import read_file
3
+ from wpctl.utils.custom_logger import get_logger
4
+
5
+ logger = get_logger(__name__)
6
+
7
+
8
+ def run(file_path: str, title: str) -> dict:
9
+ """記事を投稿する。
10
+
11
+ Args:
12
+ file_path: 投稿するファイルのパス(.txt / .html / .md)
13
+ title: 記事のタイトル
14
+
15
+ Returns:
16
+ WordPress API のレスポンスデータ
17
+ """
18
+ content = read_file(file_path)
19
+ api = ApiWordpress()
20
+ result = api.create_post(title=title, content=content)
21
+ logger.info(f"記事を投稿しました: id={result.get('id')}, link={result.get('link')}")
22
+ return result
@@ -0,0 +1,25 @@
1
+ from wpctl.api.api_wordpress import ApiWordpress
2
+ from wpctl.libs.file_reader import read_file
3
+ from wpctl.utils.custom_logger import get_logger
4
+
5
+ logger = get_logger(__name__)
6
+
7
+
8
+ def run(file_path: str, post_id: int, title: str) -> dict:
9
+ """記事を更新する。
10
+
11
+ Args:
12
+ file_path: 投稿するファイルのパス(.txt / .html / .md)
13
+ post_id: 更新する記事のID
14
+ title: 記事のタイトル
15
+
16
+ Returns:
17
+ WordPress API のレスポンスデータ
18
+ """
19
+ content = read_file(file_path)
20
+ api = ApiWordpress()
21
+ result = api.update_post(post_id=post_id, title=title, content=content)
22
+ logger.info(
23
+ f"記事を更新しました: id={result.get('id')}, link={result.get('link')}"
24
+ )
25
+ return result
wpctl/libs/__init__.py ADDED
File without changes
@@ -0,0 +1,48 @@
1
+ from pathlib import Path
2
+
3
+ from wpctl.libs.md_to_html import convert
4
+ from wpctl.utils.custom_logger import get_logger
5
+
6
+ logger = get_logger(__name__)
7
+
8
+ SUPPORTED_EXTENSIONS = {".txt", ".html", ".md"}
9
+
10
+
11
+ class FileReadError(Exception):
12
+ """ファイル読み込みエラー。"""
13
+
14
+
15
+ def read_file(file_path: str) -> str:
16
+ """ファイルを読み込んで文字列として返す。
17
+
18
+ Markdownファイルはサニタイズ済みHTMLに変換して返す。
19
+ .txt / .html はそのまま返す。
20
+
21
+ Args:
22
+ file_path: 読み込むファイルのパス
23
+
24
+ Returns:
25
+ ファイルの内容(Markdownの場合はHTML変換済み)
26
+
27
+ Raises:
28
+ FileReadError: ファイルが存在しない、または未対応の拡張子の場合
29
+ """
30
+ path = Path(file_path)
31
+
32
+ if not path.exists():
33
+ raise FileReadError(f"ファイルが見つかりません: {file_path}")
34
+
35
+ ext = path.suffix.lower()
36
+ if ext not in SUPPORTED_EXTENSIONS:
37
+ supported = ", ".join(sorted(SUPPORTED_EXTENSIONS))
38
+ raise FileReadError(
39
+ f"未対応のファイル形式です: {ext}(対応形式: {supported})"
40
+ )
41
+
42
+ text = path.read_text(encoding="utf-8")
43
+ logger.info(f"ファイルを読み込みました: {file_path}")
44
+
45
+ if ext == ".md":
46
+ return convert(text)
47
+
48
+ return text
@@ -0,0 +1,35 @@
1
+ import markdown
2
+ import nh3
3
+
4
+ _ALLOWED_TAGS = {
5
+ "p", "br", "strong", "b", "em", "i", "u", "s",
6
+ "h1", "h2", "h3", "h4", "h5", "h6",
7
+ "ul", "ol", "li", "dl", "dt", "dd",
8
+ "blockquote", "pre", "code",
9
+ "table", "thead", "tbody", "tr", "th", "td",
10
+ "a", "img", "hr",
11
+ }
12
+
13
+ _ALLOWED_ATTRIBUTES: dict[str, set[str]] = {
14
+ "a": {"href", "title", "target"},
15
+ "img": {"src", "alt", "width", "height"},
16
+ "td": {"colspan", "rowspan"},
17
+ "th": {"colspan", "rowspan"},
18
+ "code": {"class"},
19
+ "pre": {"class"},
20
+ }
21
+
22
+
23
+ def convert(text: str) -> str:
24
+ """MarkdownをサニタイズされたHTMLに変換する。
25
+
26
+ script タグ・イベント属性・外部JavaScript埋め込みを除去する。
27
+
28
+ Args:
29
+ text: Markdown形式のテキスト
30
+
31
+ Returns:
32
+ サニタイズ済みのHTML文字列
33
+ """
34
+ html = markdown.markdown(text, extensions=["tables", "fenced_code"])
35
+ return nh3.clean(html, tags=_ALLOWED_TAGS, attributes=_ALLOWED_ATTRIBUTES)
wpctl/main.py ADDED
@@ -0,0 +1,82 @@
1
+ import argparse
2
+ import os
3
+ import sys
4
+
5
+ from wpctl.run import run
6
+ from wpctl.utils.custom_logger import get_logger
7
+
8
+ logger = get_logger(__name__)
9
+
10
+ _REQUIRED_ENV_VARS = ["WP_SITE_URL", "WP_USER", "WP_APP_PASSWORD"]
11
+
12
+
13
+ def _validate_env() -> None:
14
+ """必須環境変数が設定されているか検証する。
15
+
16
+ Raises:
17
+ SystemExit: 未設定の環境変数が存在する場合
18
+ """
19
+ missing = [var for var in _REQUIRED_ENV_VARS if not os.environ.get(var)]
20
+ if missing:
21
+ for var in missing:
22
+ print(f"Error: 環境変数 {var} が設定されていません。", file=sys.stderr)
23
+ sys.exit(1)
24
+
25
+
26
+ def _build_parser() -> argparse.ArgumentParser:
27
+ """CLIパーサーを構築する。
28
+
29
+ Returns:
30
+ ArgumentParser: 構築済みのパーサー
31
+ """
32
+ parser = argparse.ArgumentParser(
33
+ prog="wpctl",
34
+ description="WordPress APIを活用して記事を投稿・管理するCLIツール",
35
+ )
36
+ subparsers = parser.add_subparsers(dest="command")
37
+
38
+ post_parser = subparsers.add_parser("post", help="記事を管理する")
39
+ post_subparsers = post_parser.add_subparsers(dest="subcommand")
40
+
41
+ create_parser = post_subparsers.add_parser("create", help="記事を投稿する")
42
+ create_parser.add_argument(
43
+ "file_path", metavar="FilePath", help="投稿するファイルのパス"
44
+ )
45
+ create_parser.add_argument(
46
+ "--title", "-t",
47
+ default="タイトル未設定",
48
+ help="記事のタイトル(デフォルト: タイトル未設定)",
49
+ )
50
+
51
+ update_parser = post_subparsers.add_parser("update", help="記事を更新する")
52
+ update_parser.add_argument(
53
+ "file_path", metavar="FilePath", help="投稿するファイルのパス"
54
+ )
55
+ update_parser.add_argument(
56
+ "--id", dest="post_id", type=int, required=True, help="更新する記事のID"
57
+ )
58
+ update_parser.add_argument(
59
+ "--title", "-t",
60
+ default="タイトル未設定",
61
+ help="記事のタイトル(デフォルト: タイトル未設定)",
62
+ )
63
+
64
+ return parser
65
+
66
+
67
+ def main():
68
+ """エントリーポイント。CLI引数を解析してrunに渡す。"""
69
+ parser = _build_parser()
70
+ args = parser.parse_args()
71
+
72
+ if args.command is None:
73
+ parser.print_help()
74
+ return
75
+
76
+ _validate_env()
77
+ logger.info(f"wpctl {args.command} {args.subcommand}")
78
+ run(args)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ main()
wpctl/run.py ADDED
@@ -0,0 +1,67 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from wpctl.api.api_wordpress import WordPressAPIError
5
+ from wpctl.libs.file_reader import FileReadError
6
+ from wpctl.utils.custom_logger import get_logger
7
+
8
+ logger = get_logger(__name__)
9
+
10
+
11
+ def run(args: argparse.Namespace) -> None:
12
+ """ツールのロジック開始点。コマンドに応じた処理を呼び出す。
13
+
14
+ Args:
15
+ args: argparseで解析された引数
16
+ """
17
+ if args.command == "post":
18
+ _run_post(args)
19
+ else:
20
+ raise ValueError(f"Unknown command: {args.command}")
21
+
22
+
23
+ def _run_post(args: argparse.Namespace) -> None:
24
+ """post コマンドのサブコマンドを振り分ける。
25
+
26
+ Args:
27
+ args: argparseで解析された引数
28
+ """
29
+ if args.subcommand == "create":
30
+ _run_post_create(args)
31
+ elif args.subcommand == "update":
32
+ _run_post_update(args)
33
+ else:
34
+ raise ValueError(f"Unknown subcommand: {args.subcommand}")
35
+
36
+
37
+ def _run_post_create(args: argparse.Namespace) -> None:
38
+ """記事を投稿する。
39
+
40
+ Args:
41
+ args: argparseで解析された引数
42
+ file_path: 投稿するファイルのパス
43
+ title: 記事のタイトル
44
+ """
45
+ from wpctl.commands.create import run as create_run
46
+ try:
47
+ create_run(file_path=args.file_path, title=args.title)
48
+ except (FileReadError, WordPressAPIError) as e:
49
+ logger.error(str(e))
50
+ sys.exit(1)
51
+
52
+
53
+ def _run_post_update(args: argparse.Namespace) -> None:
54
+ """記事を更新する。
55
+
56
+ Args:
57
+ args: argparseで解析された引数
58
+ file_path: 投稿するファイルのパス
59
+ post_id: 更新する記事のID
60
+ title: 記事のタイトル
61
+ """
62
+ from wpctl.commands.update import run as update_run
63
+ try:
64
+ update_run(file_path=args.file_path, post_id=args.post_id, title=args.title)
65
+ except (FileReadError, WordPressAPIError) as e:
66
+ logger.error(str(e))
67
+ sys.exit(1)
File without changes
@@ -0,0 +1,118 @@
1
+ import os
2
+ import logging
3
+ import json
4
+ import functools
5
+ from .singleton import Singleton
6
+
7
+ LEVEL_MAPPING = {
8
+ "CRITICAL": logging.CRITICAL,
9
+ "ERROR": logging.ERROR,
10
+ "WARNING": logging.WARNING,
11
+ "INFO": logging.INFO,
12
+ "DEBUG": logging.DEBUG,
13
+ "NOTSET": logging.NOTSET,
14
+ }
15
+
16
+
17
+ class CoogelCustomLogger:
18
+ """Google Cloud Functions用のシンプルなカスタムロガー"""
19
+
20
+ def __init__(self, name="main"):
21
+ self.logger = logging.getLogger(name)
22
+ self.logger.setLevel(logging.INFO)
23
+
24
+ handler = logging.StreamHandler()
25
+ handler.setLevel(logging.INFO)
26
+ # メッセージのみ(フォーマットなし)
27
+ formatter = logging.Formatter("%(message)s")
28
+ handler.setFormatter(formatter)
29
+
30
+ if not self.logger.handlers:
31
+ self.logger.addHandler(handler)
32
+
33
+ def _log(self, message, level="INFO", **fields):
34
+ payload = {"serverity": level, "message": f"{message}", **fields}
35
+ self.logger.info(json.dumps(payload, ensure_ascii=False))
36
+
37
+ def info(self, message, **fields):
38
+ self._log(message, level="INFO", **fields)
39
+
40
+ def warning(self, message, **fields):
41
+ self._log(message, level="WARNING", **fields)
42
+
43
+ def error(self, message, **fields):
44
+ self._log(message, level="ERROR", **fields)
45
+
46
+ def exception(self, message, **fields):
47
+ payload = {"serverity": "ERROR", "message": f"{message}", **fields}
48
+ self.logger.info(json.dumps(payload, ensure_ascii=False), exc_info=True)
49
+
50
+ def debug(self, message, **fields):
51
+ self._log(message, level="DEBUG", **fields)
52
+
53
+ def setLevel(self, level):
54
+ self.logger.setLevel(level)
55
+
56
+
57
+ class CustomLogger(Singleton):
58
+ """
59
+ Singleton logger class that initializes a logger with a specified name
60
+ and log file.It provides a method to log entry and exit of functions.
61
+ """
62
+
63
+ _initialized = False
64
+
65
+ def __init__(self, name="main", log_file=None, level=None):
66
+ if hasattr(self, "_initialized") and self._initialized:
67
+ return # すでに初期化済みなら何もしない
68
+
69
+ if os.getenv("ENV", "local") == "local":
70
+ if level is None and os.getenv("LOG_LEVEL"):
71
+ level_str = os.getenv("LOG_LEVEL").upper()
72
+ _level = LEVEL_MAPPING.get(level_str, logging.INFO)
73
+ elif level is None:
74
+ _level = logging.INFO
75
+ else:
76
+ _level = level
77
+
78
+ self.logger = logging.getLogger(name)
79
+ self.logger.setLevel(_level)
80
+ self.logger.propagate = False
81
+
82
+ formatter = logging.Formatter(
83
+ "%(asctime)s %(levelname)s [%(filename)s:%(lineno)3d]: %(message)s"
84
+ )
85
+
86
+ # Console handler
87
+ ch = logging.StreamHandler()
88
+ ch.setFormatter(formatter)
89
+ self.logger.addHandler(ch)
90
+
91
+ # File handler
92
+ if log_file:
93
+ fh = logging.FileHandler(log_file, encoding="utf-8")
94
+ fh.setFormatter(formatter)
95
+ self.logger.addHandler(fh)
96
+
97
+ self._initialized = True
98
+ elif os.getenv("ENV") in ["dev", "prd"]:
99
+ self.logger = CoogelCustomLogger(name)
100
+ self._initialized = True
101
+
102
+ def get_logger(self):
103
+ return self.logger
104
+
105
+ def log_entry_exit(self, func):
106
+ @functools.wraps(func)
107
+ def wrapper(*args, **kwargs):
108
+ self.logger.info(f"Enter: {func.__qualname__}")
109
+ result = func(*args, **kwargs)
110
+ self.logger.info(f"Exit: {func.__qualname__}")
111
+ return result
112
+
113
+ return wrapper
114
+
115
+
116
+ def get_logger(name="main", log_file=None, level=None):
117
+ custom_logger = CustomLogger(name, log_file, level)
118
+ return custom_logger.get_logger()
@@ -0,0 +1,23 @@
1
+ """Singleton pattern implementation in Python.
2
+ This implementation is thread-safe and
3
+ ensures that only one instance of the class is created.
4
+
5
+ Singleton が提供するのは「同じインスタンスを返す仕組み」
6
+ * __init__() は毎回呼ばれる(多くの人が意図しない動作)
7
+ * __init__の2回目は_initialized というフラグは 使う側で管理する必要がある。
8
+ """
9
+
10
+ import threading
11
+
12
+
13
+ class Singleton(object):
14
+ """シングルトンパターンの基底クラス"""
15
+ _instances = {}
16
+ _lock = threading.Lock()
17
+
18
+ def __new__(cls, *args, **kwargs):
19
+ if cls not in cls._instances:
20
+ with cls._lock:
21
+ if cls not in cls._instances: # ダブルチェック
22
+ cls._instances[cls] = super(Singleton, cls).__new__(cls)
23
+ return cls._instances[cls]
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: wpctl
3
+ Version: 1.0.0
4
+ Summary: WordPress APIを活用して記事を投稿・管理するCLIツール
5
+ Author-email: "Ryoh.Ya" <dev.p.ry.yamafuji@gmail.com>
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: markdown>=3.7
10
+ Requires-Dist: nh3>=0.2.18
11
+ Requires-Dist: requests>=2.32
12
+ Dynamic: license-file
13
+
14
+ # wpctl
15
+
16
+ WordPress APIを活用して、記事を投稿・管理するCLIツール。
17
+
18
+ ## 概要
19
+
20
+ MarkDown / テキスト / HTMLファイルを読み込み、WordPress REST APIを通じて記事の投稿・更新を行います。
21
+
22
+ ## 機能
23
+
24
+ | 機能 | コマンド | 説明 |
25
+ | -------- | ------------------- | ------------------------------------------ |
26
+ | 記事投稿 | `wpctl post create` | Markdownなどのファイルを記事として投稿する |
27
+ | 記事更新 | `wpctl post update` | 既存の記事をファイルの内容で更新する |
28
+
29
+ ## インストール
30
+
31
+ ```sh
32
+ pip install .
33
+ ```
34
+
35
+ ## 使い方
36
+
37
+ ### 記事を投稿する
38
+
39
+ ```sh
40
+ wpctl post create [-t "記事のタイトル"] <FilePath>
41
+ ```
42
+
43
+ | 引数 | オプション | 必須 | 説明 |
44
+ | ---- | ---------- | ---- | ---- |
45
+ | `FilePath` | | true | 投稿するファイルのパス |
46
+ | `title` | `--title`, `-t` | | 記事タイトル(デフォルト: `タイトル未設定`)|
47
+
48
+ ### 記事を更新する
49
+
50
+ ```sh
51
+ wpctl post update --id <ID> [-t "記事のタイトル"] <FilePath>
52
+ ```
53
+
54
+ | 引数 | オプション | 必須 | 説明 |
55
+ | ---- | ---------- | ---- | ---- |
56
+ | `FilePath` | | true | 投稿するファイルのパス |
57
+ | `id` | `--id` | true | 更新対象の記事ID |
58
+ | `title` | `--title`, `-t` | | 記事タイトル(デフォルト: `タイトル未設定`)|
59
+
60
+ ## 環境変数
61
+
62
+ | 環境変数 | 必須 | 説明 |
63
+ | ----------------- | ---- | --------------------------- |
64
+ | `WP_USER` | true | WordPressのユーザー名 |
65
+ | `WP_APP_PASSWORD` | true | WordPressのアプリパスワード |
66
+
67
+ > `.env` ファイルは非対応です。あらかじめシェルの環境変数に設定してください。
68
+
69
+ ## 対応ファイル形式
70
+
71
+ - `.txt`
72
+ - `.md`
73
+ - `.html`
74
+
75
+ ## Tool
76
+
77
+ ### Test
78
+
79
+ ```sh
80
+ pip install -r requirements-dev.txt
81
+ pytest tests/
82
+ # ログを出力する場合
83
+ # pytest -s tests/
84
+ ```
85
+
86
+ ### Lint
87
+
88
+ ```sh
89
+ pip install -r requirements-dev.txt
90
+ ruff check .
91
+ # 自動修正する場合
92
+ ruff check . --fix
93
+ ```
@@ -0,0 +1,20 @@
1
+ wpctl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ wpctl/main.py,sha256=8tGR2JJ1BISwvl9lAmUDOpabO1B8o0yUCAXsWgbQouI,2459
3
+ wpctl/run.py,sha256=YP-hnoy7I1fbu24yx7rbz7_JGPnSf98MOIg94a8nBY8,1970
4
+ wpctl/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ wpctl/api/api_wordpress.py,sha256=CYL06F4rcgePV5QgU7r1Vo479lCTAG0QjPb3n5x-CQg,3406
6
+ wpctl/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ wpctl/commands/create.py,sha256=7LW_vvrpcKBlNUAAYlXJzV8kgYc837H0rdfuaEkfF9s,685
8
+ wpctl/commands/update.py,sha256=LvtGfS7AyeI77fFZo6YZ1YIZh5LoX8UIMKbnC25LWdM,771
9
+ wpctl/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ wpctl/libs/file_reader.py,sha256=U-F-YnHfk7YepnvxZjrfGinwBRhUnreWbkarWc_BZvg,1349
11
+ wpctl/libs/md_to_html.py,sha256=1AfrO0hvqCDaNVMJD6FDgiavj012DawIg9qejBT0ysk,990
12
+ wpctl/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ wpctl/utils/custom_logger.py,sha256=P7la4o0pjs5kxQotaVH88ETwgmIYLpTOS9dY2xHl6ho,3894
14
+ wpctl/utils/singleton.py,sha256=JvDiqfb-S0YD_Zdbg9gbHEPnNi9hzdF5z5AJykJ4tEY,866
15
+ wpctl-1.0.0.dist-info/licenses/LICENSE,sha256=xKH_XCNws7RSp2D0P8TspewAyV39RMjgiBb4FaHHigk,1066
16
+ wpctl-1.0.0.dist-info/METADATA,sha256=haxkQ3YVvY56thTHs3FW72qIBxWOwmypmkYcm1jQ-yI,2483
17
+ wpctl-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
18
+ wpctl-1.0.0.dist-info/entry_points.txt,sha256=725QlKhDbBtQvfUYuX_O6K6fEAVt6J49710BseSamF8,42
19
+ wpctl-1.0.0.dist-info/top_level.txt,sha256=jQhFIjIeMTsdl9nngp04azo8gZfXlkeY1yyEE1q8-H8,6
20
+ wpctl-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ wpctl = wpctl.main:main
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 templates
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ wpctl