diff --git a/Makefile b/Makefile index 6a81844..c3696b3 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,6 @@ format: lint: poetry run ruff check ${SOURCE_FILES} ${TEST_FILES} poetry run mypy ${SOURCE_FILES} - poetry run pylint --jobs=0 ${SOURCE_FILES} test: # 並列実行してレポートも出力する diff --git a/README.md b/README.md index ba02619..fdf9127 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # confluence-cli -来栖川電算が利用しているConfluence v6.15.7を操作するためのCLIです。 +Confluence v6.15.7を操作するためのCLIです。 -[![Build Status](https://app.travis-ci.com/kurusugawa-computer/confluence-cli.svg?branch=main)](https://app.travis-ci.com/kurusugawa-computer/confluence-cli) +[![Test](https://github.com/kurusugawa-computer/confluence-cli/actions/workflows/test.yml/badge.svg)](https://github.com/kurusugawa-computer/confluence-cli/actions/workflows/test.yml) [![PyPI version](https://badge.fury.io/py/kci-confluence-cli.svg)](https://badge.fury.io/py/kci-confluence-cli) [![Python Versions](https://img.shields.io/pypi/pyversions/kci-confluence-cli.svg)](https://pypi.org/project/kci-confluence-cli/) [![Documentation Status](https://readthedocs.org/projects/confluence-cli/badge/?version=latest)](https://confluence-cli.readthedocs.io/ja/latest/?badge=latest) @@ -29,7 +29,7 @@ export CONFLUENCE_BASE_URL="https://your-domain.com/confluence" ページ本文を取得: ```bash -$ confluence page get_body --page_id 12345 +$ confluence page get_body --page_id 123456 ``` # Documentation diff --git a/confluence/__main__.py b/confluence/__main__.py index 7ba9c92..d6ab924 100644 --- a/confluence/__main__.py +++ b/confluence/__main__.py @@ -3,7 +3,6 @@ import argparse import logging from collections.abc import Sequence -from typing import Optional import confluence import confluence.attachment.subcommand @@ -30,7 +29,7 @@ def create_parser() -> argparse.ArgumentParser: return parser -def main(arguments: Optional[Sequence[str]] = None): # noqa: ANN201 +def main(arguments: Sequence[str] | None = None): # noqa: ANN201 parser = create_parser() if arguments is None: args = parser.parse_args() diff --git a/confluence/attachment/create_attachment.py b/confluence/attachment/create_attachment.py index ec45c24..63c7a71 100644 --- a/confluence/attachment/create_attachment.py +++ b/confluence/attachment/create_attachment.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any -import confluence +from confluence.common import cli from confluence.common.api import Api from confluence.common.cli import create_api_instance @@ -27,9 +27,9 @@ def create_attachments_from_file_list( try: api.create_attachment(content_id, file, query_params=query_params, mime_type=mime_type) - logger.debug(f"{index+1}件目: '{file}'をアップロードしました。") + logger.debug(f"{index + 1}件目: '{file}'をアップロードしました。") if (index + 1) % 10 == 0: - logger.info(f"{index+1}件目のファイルのアップロードが完了しました。") + logger.info(f"{index + 1}件目のファイルのアップロードが完了しました。") except Exception: logger.warning(f"'{file}'のアップロードに失敗しました。", exc_info=True) @@ -62,9 +62,9 @@ def create_attachments_from_directory( for index, file in enumerate(files): try: api.create_attachment(content_id, file, query_params=query_params, mime_type=mime_type) - logger.debug(f"{index+1}件目: '{file}'をアップロードしました。") + logger.debug(f"{index + 1}件目: '{file}'をアップロードしました。") if (index + 1) % 10 == 0: - logger.info(f"{index+1}件目のファイルのアップロードが完了しました。") + logger.info(f"{index + 1}件目のファイルのアップロードが完了しました。") except Exception: logger.warning(f"'{file}'のアップロードに失敗しました。", exc_info=True) @@ -107,7 +107,7 @@ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse subcommand_name = "create" subcommand_help = "添付ファイルを作成します。" - parser = confluence.common.cli.add_parser(subparsers, subcommand_name, subcommand_help) + parser = cli.add_parser(subparsers, subcommand_name, subcommand_help) add_arguments_to_parser(parser) return parser diff --git a/confluence/attachment/delete_attachment.py b/confluence/attachment/delete_attachment.py index c3a5d15..909dbd6 100644 --- a/confluence/attachment/delete_attachment.py +++ b/confluence/attachment/delete_attachment.py @@ -4,7 +4,7 @@ import logging from typing import Any -import confluence +from confluence.common import cli from confluence.common.api import Api from confluence.common.cli import create_api_instance, prompt_yesnoall @@ -54,15 +54,15 @@ def main(args: argparse.Namespace) -> None: yes, all_yes = prompt_yesnoall(confirm_message) if yes or all_yes: - logger.debug(f"{index+1}件目: id='{attachment_id}', title='{attachment_title}'をゴミ箱に移動します。 ") + logger.debug(f"{index + 1}件目: id='{attachment_id}', title='{attachment_title}'をゴミ箱に移動します。 ") api.delete_content(attachment_id, query_params={"status": "current"}) if is_purged: - logger.debug(f"{index+1}件目: id='{attachment_id}', title='{attachment_title}'をゴミ箱から完全に削除します。 ") + logger.debug(f"{index + 1}件目: id='{attachment_id}', title='{attachment_title}'をゴミ箱から完全に削除します。 ") api.delete_content(attachment_id, query_params={"status": "trashed"}) success_count += 1 except Exception: - logger.warning(f"{index+1}件目: id='{attachment_id}', title='{attachment_title}'の削除に失敗しました。", exc_info=True) + logger.warning(f"{index + 1}件目: id='{attachment_id}', title='{attachment_title}'の削除に失敗しました。", exc_info=True) continue logger.info(f"{success_count}/{len(results)} 件の添付ファイルを削除しました。") @@ -81,7 +81,7 @@ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse subcommand_name = "delete" subcommand_help = "添付ファイルを削除します。" - parser = confluence.common.cli.add_parser(subparsers, subcommand_name, subcommand_help) + parser = cli.add_parser(subparsers, subcommand_name, subcommand_help) add_arguments_to_parser(parser) return parser diff --git a/confluence/attachment/get_attachment.py b/confluence/attachment/get_attachment.py index 87c70f8..375089d 100644 --- a/confluence/attachment/get_attachment.py +++ b/confluence/attachment/get_attachment.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any -import confluence +from confluence.common import cli from confluence.common.cli import create_api_instance from confluence.common.utils import print_json @@ -49,7 +49,7 @@ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse subcommand_name = "get" subcommand_help = "添付ファイルの情報を取得します。" - parser = confluence.common.cli.add_parser(subparsers, subcommand_name, subcommand_help) + parser = cli.add_parser(subparsers, subcommand_name, subcommand_help) add_arguments_to_parser(parser) return parser diff --git a/confluence/attachment/subcommand.py b/confluence/attachment/subcommand.py index ce9999d..4777c6f 100644 --- a/confluence/attachment/subcommand.py +++ b/confluence/attachment/subcommand.py @@ -1,12 +1,12 @@ from __future__ import annotations import argparse -from typing import Optional import confluence import confluence.attachment.create_attachment import confluence.attachment.delete_attachment import confluence.attachment.get_attachment +from confluence.common import cli def add_arguments_to_parser(parser: argparse.ArgumentParser): # noqa: ANN201 @@ -18,10 +18,10 @@ def add_arguments_to_parser(parser: argparse.ArgumentParser): # noqa: ANN201 confluence.attachment.get_attachment.add_parser(subparsers) -def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser: +def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser: subcommand_name = "attachment" subcommand_help = "添付ファイルに関するサブコマンド" - parser = confluence.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, is_subcommand=False) + parser = cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, is_subcommand=False) add_arguments_to_parser(parser) return parser diff --git a/confluence/common/api.py b/confluence/common/api.py index 5b15105..5b2f709 100644 --- a/confluence/common/api.py +++ b/confluence/common/api.py @@ -5,7 +5,7 @@ import mimetypes import time from pathlib import Path -from typing import Any, Optional +from typing import Any from requests_toolbelt import sessions @@ -53,8 +53,8 @@ def _request( http_method: str, url: str, *, - headers: Optional[dict[str, Any]] = None, - params: Optional[QueryParams] = None, + headers: dict[str, Any] | None = None, + params: QueryParams | None = None, data: Any = None, # noqa: ANN401 **kwargs, ) -> Any: # noqa: ANN401 @@ -101,12 +101,12 @@ def _request( response.raise_for_status() return response - def get_attachments(self, content_id: str, *, query_params: Optional[QueryParams] = None) -> dict[str, Any]: + def get_attachments(self, content_id: str, *, query_params: QueryParams | None = None) -> dict[str, Any]: url = f"content/{content_id}/child/attachment" return self._request("get", url, params=query_params).json() def create_attachment( - self, content_id: str, file: Path, *, query_params: Optional[QueryParams] = None, mime_type: Optional[str] = None + self, content_id: str, file: Path, *, query_params: QueryParams | None = None, mime_type: str | None = None ) -> dict[str, Any]: """ Args: @@ -122,7 +122,7 @@ def create_attachment( files = {"file": (file.name, f, new_mime_type)} return self._request("post", url, params=query_params, files=files, headers=headers).json() - def get_content(self, *, query_params: Optional[QueryParams] = None) -> list[dict[str, Any]]: + def get_content(self, *, query_params: QueryParams | None = None) -> list[dict[str, Any]]: """ Returns a paginated list of Content. @@ -130,7 +130,7 @@ def get_content(self, *, query_params: Optional[QueryParams] = None) -> list[dic """ return self._request("get", "content", params=query_params).json() - def get_content_by_id(self, content_id: str, *, query_params: Optional[QueryParams] = None) -> dict[str, Any]: + def get_content_by_id(self, content_id: str, *, query_params: QueryParams | None = None) -> dict[str, Any]: """ Returns a piece of Content. @@ -138,9 +138,7 @@ def get_content_by_id(self, content_id: str, *, query_params: Optional[QueryPara """ return self._request("get", f"content/{content_id}", params=query_params).json() - def update_content( - self, content_id: str, *, query_params: Optional[QueryParams] = None, request_body: Optional[RequestBody] = None - ) -> dict[str, Any]: + def update_content(self, content_id: str, *, query_params: QueryParams | None = None, request_body: RequestBody | None = None) -> dict[str, Any]: """ Updates a piece of Content, including changes to content status @@ -148,7 +146,7 @@ def update_content( """ return self._request("put", f"content/{content_id}", params=query_params, json=request_body).json() - def delete_content(self, content_id: str, *, query_params: Optional[QueryParams] = None) -> None: + def delete_content(self, content_id: str, *, query_params: QueryParams | None = None) -> None: """ Trashes or purges a piece of Content, based on its {@link ContentType} and {@link ContentStatus}. @@ -159,14 +157,14 @@ def delete_content(self, content_id: str, *, query_params: Optional[QueryParams] """ self._request("delete", f"content/{content_id}", params=query_params) - def get_content_history(self, content_id: str, *, query_params: Optional[QueryParams] = None): # noqa: ANN201 + def get_content_history(self, content_id: str, *, query_params: QueryParams | None = None): # noqa: ANN201 """Returns the history of a particular piece of content https://docs.atlassian.com/ConfluenceServer/rest/6.15.7/#api/content-getHistory """ return self._request("get", f"content/{content_id}/history", params=query_params).json() - def search_content(self, *, query_params: Optional[QueryParams] = None) -> dict[str, Any]: + def search_content(self, *, query_params: QueryParams | None = None) -> dict[str, Any]: """ Fetch a list of content using the Confluence Query Language (CQL) diff --git a/confluence/common/cli.py b/confluence/common/cli.py index e943ca9..fdef5d4 100644 --- a/confluence/common/cli.py +++ b/confluence/common/cli.py @@ -8,7 +8,6 @@ import getpass import logging import os -from typing import Optional from more_itertools import first_true @@ -53,12 +52,12 @@ def _get_help_string(self, action) -> str: # noqa: ANN001 def add_parser( - subparsers: Optional[argparse._SubParsersAction], + subparsers: argparse._SubParsersAction | None, command_name: str, command_help: str, - description: Optional[str] = None, + description: str | None = None, is_subcommand: bool = True, - epilog: Optional[str] = None, + epilog: str | None = None, ) -> argparse.ArgumentParser: """ サブコマンド用にparserを追加する @@ -215,8 +214,7 @@ def get_confluence_base_url_from_stdin() -> str: def format_url(url: str) -> str: url = url.strip() - if url.endswith("/"): - url = url[:-1] + url = url.removesuffix("/") return url confluence_base_url = args.confluence_base_url diff --git a/confluence/common/utils.py b/confluence/common/utils.py index 04bd75c..192d169 100644 --- a/confluence/common/utils.py +++ b/confluence/common/utils.py @@ -4,7 +4,7 @@ import logging.config import pkgutil from pathlib import Path -from typing import Any, Optional, TypeVar +from typing import Any, TypeVar import yaml @@ -28,7 +28,7 @@ def read_lines_except_blank_line(filepath: str) -> list[str]: return [line for line in lines if line != ""] -def output_string(target: str, output: Optional[Path] = None) -> None: +def output_string(target: str, output: Path | None = None) -> None: """ 文字列を出力する。 @@ -45,7 +45,7 @@ def output_string(target: str, output: Optional[Path] = None) -> None: logger.info(f"{output} に出力しました。") -def print_json(target: Any, is_pretty: bool = False, output: Optional[Path] = None) -> None: # noqa: ANN401 +def print_json(target: Any, is_pretty: bool = False, output: Path | None = None) -> None: # noqa: ANN401 """ JSONを出力する。 diff --git a/confluence/content/get_content_by_id.py b/confluence/content/get_content_by_id.py index e7b96f2..097dd77 100644 --- a/confluence/content/get_content_by_id.py +++ b/confluence/content/get_content_by_id.py @@ -4,7 +4,7 @@ import logging from pathlib import Path -import confluence +from confluence.common import cli from confluence.common.cli import create_api_instance from confluence.common.utils import print_json @@ -31,7 +31,7 @@ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse subcommand_name = "get_by_id" subcommand_help = "コンテンツの情報を取得します。" - parser = confluence.common.cli.add_parser(subparsers, subcommand_name, subcommand_help) + parser = cli.add_parser(subparsers, subcommand_name, subcommand_help) add_arguments_to_parser(parser) return parser diff --git a/confluence/content/subcommand.py b/confluence/content/subcommand.py index 323c11d..5051d56 100644 --- a/confluence/content/subcommand.py +++ b/confluence/content/subcommand.py @@ -1,10 +1,10 @@ from __future__ import annotations import argparse -from typing import Optional import confluence import confluence.content.get_content_by_id +from confluence.common import cli def add_arguments_to_parser(parser: argparse.ArgumentParser): # noqa: ANN201 @@ -14,10 +14,10 @@ def add_arguments_to_parser(parser: argparse.ArgumentParser): # noqa: ANN201 confluence.content.get_content_by_id.add_parser(subparsers) -def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser: +def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser: subcommand_name = "content" subcommand_help = "コンテンツ(ページ、ブログ、添付ファイルなど)に関するサブコマンド" - parser = confluence.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, is_subcommand=False) + parser = cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, is_subcommand=False) add_arguments_to_parser(parser) return parser diff --git a/confluence/local/subcommand.py b/confluence/local/subcommand.py index f05d4d9..ac4ffb9 100644 --- a/confluence/local/subcommand.py +++ b/confluence/local/subcommand.py @@ -1,7 +1,6 @@ from __future__ import annotations import argparse -from typing import Optional import confluence import confluence.attachment.get_attachment @@ -15,7 +14,7 @@ def add_arguments_to_parser(parser: argparse.ArgumentParser): # noqa: ANN201 confluence.local.convert_html_to_xml.add_parser(subparsers) -def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser: +def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser: subcommand_name = "local" subcommand_help = "Confluenceにアクセスせずにローカル上で完結するコマンド" diff --git a/confluence/page/get_page_body.py b/confluence/page/get_page_body.py index 7615673..5a87fe0 100644 --- a/confluence/page/get_page_body.py +++ b/confluence/page/get_page_body.py @@ -1,11 +1,11 @@ -from __future__ import annotations - import argparse import logging from enum import Enum from pathlib import Path -import confluence +from lxml import etree, html + +from confluence.common import cli from confluence.common.cli import create_api_instance from confluence.common.utils import output_string @@ -13,12 +13,44 @@ class BodyRepresentation(Enum): + """ + ページの中身の表現方法。 + 各フィールドの説明は以下のドキュメントの説明を引用しています。 + https://docs.atlassian.com/atlassian-confluence/6.6.0/com/atlassian/confluence/api/model/content/ContentRepresentation.html + """ + STORAGE = "storage" + """Raw database format, for content that stores data in our XML storage format""" VIEW = "view" + """HTML representation for viewing in a web page""" EDITOR = "editor" + """Representation suitable for use in the rich text editor""" EXPORT_VIEW = "export_view" + """HTML representation for viewing, but with absolute urls, instead of relative urls in the markup.""" STYLED_VIEW = "styled_view" + """A rendered view that includes inline styles in a