paint-brush
Trello ボード管理用の Python CLI プログラムを作成する方法 (パート 1)@elainechan01
2,360 測定値
2,360 測定値

Trello ボード管理用の Python CLI プログラムを作成する方法 (パート 1)

Elaine Yun Ru Chan19m2023/08/15
Read on Terminal Reader

長すぎる; 読むには

Wikipedia に記載されているように、「コマンドライン インターフェイス (CLI) は、ユーザーまたはクライアントからのコマンドと、テキスト行の形式でのデバイスまたはプログラムからの応答を使用して、デバイスまたはコンピューター プログラムと対話する手段です。」 言い換えれば、CLI プログラムは、ユーザーがコマンド ラインを使用して、実行命令を提供することでプログラムと対話するプログラムです。 日常的なソフトウェアの多くは、CLI プログラムとしてラップされています。たとえば、vim テキスト エディターを考えてみましょう。このツールは、UNIX システムに付属しており、ターミナルで vim <FILE> を実行するだけでアクティブ化できます。 Google Cloud CLI に関して、CLI プログラムの構造を詳しく見てみましょう。
featured image - Trello ボード管理用の Python CLI プログラムを作成する方法 (パート 1)
Elaine Yun Ru Chan HackerNoon profile picture
0-item

免責事項: このチュートリアルは、読者が Python、API、Git、単体テストの基礎知識を持っていることを前提としています。

最高にクールなアニメーションを備えたさまざまな CLI ソフトウェアに出会ったので、「ミニマルな」じゃんけん学校プロジェクトをアップグレードできるだろうか、と疑問に思いました。


こんにちは、遊びましょう!ファイターを選択してください (ジャンケン): ロック

CLI プログラムとは何ですか?

Wikipedia に記載されているように、「コマンドライン インターフェイス (CLI) は、ユーザーまたはクライアントからのコマンドと、テキスト行の形式でのデバイスまたはプログラムからの応答によって、デバイスまたはコンピューター プログラムと対話する手段です。」


言い換えれば、CLI プログラムは、ユーザーがコマンド ラインを使用して、実行命令を提供することでプログラムと対話するプログラムです。


日常的なソフトウェアの多くは、CLI プログラムとしてラップされています。たとえば、 vimテキスト エディターを考えてみましょう。このツールは、UNIX システムに付属しており、ターミナルでvim <FILE>実行するだけでアクティブ化できます。


Google Cloud CLIに関して、CLI プログラムの構造を詳しく見てみましょう。

引数

引数(パラメータ)とは、プログラムに与える情報のことです。位置によって識別されるため、位置引数と呼ばれることがよくあります。


たとえば、コア セクションでprojectプロパティを設定する場合は、 gcloud config set project <PROJECT_ID>を実行します。


特に、これを次のように翻訳できます。

口論

コンテンツ

引数0

gクラウド

引数1

構成

コマンド

コマンドは、コンピューターに指示を与える引数の配列です。


前の例に基づいて、 gcloud config set project <PROJECT_ID>を実行して、コア セクションにprojectプロパティを設定します。


つまり、 setはコマンドです。

オプションのコマンド

通常、コマンドは必須ですが、例外を設けることもできます。プログラムのユースケースに基づいて、オプションのコマンドを定義できます。


gcloud configコマンドに戻ると、公式ドキュメントに記載されているように、 gcloud configプロパティを変更できるコマンド グループです。使用方法は次のとおりです。

 gcloud config GROUP | COMMAND [GCLOUD_WIDE_FLAG … ]

ここで、 COMMAND はsetlistなどになります… ( GROUP はconfigであることに注意してください)

オプション

オプションは、コマンドの動作を変更するパラメータの文書化されたタイプです。これらは、「-」または「--」で示されるキーと値のペアです。


gcloud configコマンド グループの使用法に戻ると、この場合のオプションはGCLOUD_WIDE_FLAGです。


たとえば、コマンドの詳細な使用法と説明を表示したい場合は、 gcloud config set –helpを実行します。つまり、 --helpがオプションです。


もう 1 つの例は、特定のプロジェクトのコンピューティング セクションでゾーン プロパティを設定する場合、 gcloud config set compute <ZONE_NAME> –project=<PROJECT_ID>を実行することです。つまり、 --project<PROJECT_ID>を保持するオプションです。


また、彼らの立場は通常は重要ではないことに注意することも重要です。

必須オプション

オプションは、その名前と同様、通常はオプションですが、必須になるように調整することもできます。


たとえば、dataproc クラスタを作成する場合は、 gcloud dataproc clusters create <CLUSTER_NAME> –region=<REGION>を実行します。そして、使用方法のドキュメントに記載されているように:

 gcloud dataproc clusters create (CLUSTER: –region=REGION)

--regionフラグは、事前に構成されていない場合は必須です。

ショートオプションとロングオプション

短いオプションは-で始まり、その後に 1 つの英数字が続きます。一方、長いオプションは--で始まり、その後に複数の文字が続きます。短いオプションは、ユーザーが何を望んでいるのかがわかっている場合のショートカットとして考えてください。一方、長いオプションは読みやすいものです。


ロックを選んだんですね!コンピューターが選択を行います。

このチュートリアルを通じて何を達成できるのでしょうか?

それで、私は嘘をつきました…定番のじゃんけん CLI プログラムをアップグレードするつもりはありません。

代わりに、実際のシナリオを見てみましょう。

概要と目標

あなたのチームは Trello を使用してプロジェクトの問題と進捗状況を追跡しています。あなたのチームは、ターミナルを介して新しい GitHub リポジトリを作成するのと同じような、ボードと対話するためのより簡素化された方法を探しています。チームは、ボードの「To Do」列に新しいカードを追加できるという基本要件を備えた CLI プログラムの作成をあなたに依頼しました。


前述の要件に基づいて、要件を定義して CLI プログラムの草案を作成しましょう。


機能要件

  • ユーザーはボード上の列に新しいカードを追加できます
    • 必須入力: 列、カード名
    • オプションの入力: カードの説明、カードのラベル (既存のものから選択)

非機能要件

  • Trello アカウントへのアクセス (認証) をユーザーに求めるプログラム
  • どの Trello ボードで作業するかをユーザーに設定するよう求めるプログラム (構成)

オプションの要件

  • ユーザーはボードに新しい列を追加できます
  • ユーザーはボードに新しいラベルを追加できます
  • ユーザーはすべての列の簡略化/詳細ビューを確認できます


上記に基づいて、CLI プログラムのコマンドとオプションを次のように形式化できます。

要件に基づいた CLI 構造の詳細なテーブル ビュー


Ps 最後の 2 つの列については心配しないでください。それについては後ほど説明します…


私たちの技術スタックに関しては、これに固執します。


単体テスト

  • pytest
  • pytest-mock
  • cli-テストヘルパー

トレロ

  • py-trello (Trello SDK の Python ラッパー)

CLI

  • タイパー
  • リッチ
  • 簡易メニュー

ユーティリティ (その他)

  • Python-dotenv

タイムライン

このプロジェクトは部分的に取り組んでいきます。期待できることの一部を以下に示します。


パート1

  • py-trelloビジネスロジックの実装

パート2

  • CLIビジネスロジックの実装
  • CLI プログラムをパッケージとして配布する

パート 3

  • オプションの機能要件の実装
  • パッケージのアップデート


コンピューターがハサミを選んだのです!誰がこの戦いに勝つか見てみましょう…

始めましょう

フォルダー構造

目標は、CLI プログラムをPyPI上のパッケージとして配布することです。したがって、次のような設定が必要です。

 trellocli/ __init__.py __main__.py models.py cli.py trelloservice.py tests/ test_cli.py test_trelloservice.py README.md pyproject.toml .env .gitignore


ここでは、各ファイルおよび/またはディレクトリについて詳しく説明します。

  • trellocli : ユーザーが使用するパッケージ名として機能します (例: pip install trellocli
    • __init__.py : パッケージのルートを表し、フォルダーを Python パッケージとして準拠させます
    • __main__.py : エントリ ポイントを定義し、ユーザーが-mフラグを使用してファイル パスを指定せずにモジュールを実行できるようにします。たとえば、 python -m <module_name>python -m <parent_folder>/<module_name>.pyを置き換えます。
    • models.py : グローバルに使用されるクラス (API 応答が準拠すると予想されるモデルなど) を格納します。
    • cli.py : CLI コマンドとオプションのビジネス ロジックを保存します。
    • trelloservice.py : py-trelloと対話するビジネス ロジックを保存します。
  • tests : プログラムの単体テストを保存します。
    • test_cli.py : CLI 実装の単体テストを保存します。
    • test_trelloservice.py : py-trelloとの対話のための単体テストを保存します。
  • README.md : プログラムのドキュメントを保存します。
  • pyproject.toml : パッケージの構成と要件を保存します
  • .env : 環境変数を保存します
  • .gitignore : バージョン管理中に無視する (追跡しない) ファイルを指定します。


Python パッケージの公開の詳細については、 Geir Arne Hjelle 著の「オープンソース Python パッケージを PyPI に公開する方法」を参照してください。

設定

始める前に、パッケージのセットアップについて触れてみましょう。


パッケージ内の__init__.pyファイルから始めます。このファイルには、アプリ名やバージョンなどのパッケージの定数と変数が保存されます。この例では、次のものを初期化したいと考えています。

  • アプリ名
  • バージョン
  • SUCCESS および ERROR 定数
# trellocli/__init__.py __app_name__ = "trellocli" __version__ = "0.1.0" ( SUCCESS, TRELLO_WRITE_ERROR, TRELLO_READ_ERROR ) = range(3) ERRORS = { TRELLO_WRITE_ERROR: "Error when writing to Trello", TRELLO_READ_ERROR: "Error when reading from Trello" }


__main__.pyファイルに進むと、プログラムのメイン フローがここに保存されます。この例では、 cli.pyに呼び出し可能な関数があると想定して、CLI プログラムのエントリ ポイントを保存します。

 # trellocli/__main__.py from trellocli import cli def main(): # we'll modify this later - after the implementation of `cli.py` pass if __name__ == "__main__": main()


パッケージのセットアップが完了したので、 README.mdファイル (メインのドキュメント) の更新を見てみましょう。従う必要がある特定の構造はありませんが、優れた README は次の内容で構成されます。

  • 概要
  • インストールと要件
  • はじめにと使い方

さらに詳しく知りたい場合は、別の素晴らしい投稿を読んでください: How to Write a Good README by merlos


このプロジェクトの README を次のように構成したいと思います

<!--- README.md --> # Overview # Getting Started # Usage # Architecture ## Data Flow ## Tech Stack # Running Tests # Next Steps # References


現時点ではスケルトンをそのままにしておきます。これについては後で説明します。


次に、公式ドキュメントに基づいてパッケージのメタデータを構成しましょう

# pyproject.toml [project] name = "trellocli_<YOUR_USERNAME>" version = "0.1.0" authors = [ { name = "<YOUR_NAME>", email = "<YOUR_EMAIL>" } ] description = "Program to modify your Trello boards from your computer's command line" readme = "README.md" requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] dependencies = [] [project.urls] "Homepage" = ""


ユーザー名や名前など、変更する必要があるプレースホルダーがあることに注意してください。


話は変わりますが、ホームページの URL は今のところ空のままにしておきます。 GitHub に公開した後に変更を加えます。また、依存関係の部分は今のところ空のままにし、必要に応じて追加していきます。


リストの次は、API シークレットやキーなどの環境変数を保存する.envファイルです。このファイルには機密情報が含まれているため、Git で追跡しないように注意することが重要です。


私たちの場合、Trello 認証情報をここに保存します。 Trello でパワーアップを作成するには、このガイドに従ってください。具体的には、 py-trelloによる使用法に基づいて、アプリケーションに OAuth を使用する予定であるため、Trello と通信するには次のものが必要になります。

  • API キー (アプリケーション用)
  • API シークレット (アプリケーション用)
  • トークン (データへのアクセスを許可するためのユーザーのトークン)


API キーとシークレットを取得したら、それらを.envファイルにそのまま保存します。

 # .env TRELLO_API_KEY=<your_api_key> TRELLO_API_SECRET=<your_api_secret>


最後になりましたが、ここにあるテンプレート Python .gitignoreを使用してみましょう。これは、 .envファイルが決して追跡されないようにするために重要であることに注意してください。ある時点で.envファイルが追跡された場合、たとえ後の手順でファイルを削除したとしても、損害は発生し、悪意のある攻撃者が以前のファイルを追跡できるようになります。機密情報のパッチ。


セットアップが完了したので、変更を GitHub にプッシュしましょう。 pyproject.tomlで指定されているメタデータに応じて、それに応じてライセンスとホームページの URL を忘れずに更新してください。より良いコミットの書き方については、 「Write Better Commits, Build Better Projects」(Victoria Dye 著) を参照してください。


その他の注目すべきステップ:

単体テスト

テストの作成を開始する前に、API を使用して作業しているため、API ダウンタイムのリスクなしでプログラムをテストできるように模擬テストを実装することに注意することが重要です。 Real Python による模擬テストに関する別の優れた記事は次のとおりです: Python での外部 API の模擬


機能要件に基づいて、私たちの主な関心事は、ユーザーが新しいカードを追加できるようにすることです。 py-trelloのメソッドを参照します: add_card 。これを行うには、 Listクラスからadd_cardメソッドを呼び出す必要があります。List クラスは、 Boardクラスのget_list関数から取得できます。


要点はわかりました。最終目的地に到達するには、多くのヘルパー メソッドが必要です。それを言葉で言いましょう。

  • クライアントのトークンを取得するテスト
  • ボードを取得するテスト
  • ボードを取得するテスト
  • ボードからリストを取得するテスト
  • リストを取得するテスト
  • ボードからラベルを取得するテスト
  • ラベルを取得するテスト
  • カードを追加するテスト
  • カードにラベルを追加するテスト


単体テストを作成するときは、テストを可能な限り広範囲にわたって行う必要があることに注意することも重要です。つまり、エラーは適切に処理されますか?それは私たちのプログラムのあらゆる側面をカバーしていますか?


ただし、このチュートリアルでは、成功事例のみを確認することで物事を簡略化します。


コードに入る前に、 pyproject.tomlファイルを変更して、単体テストの作成/実行に必要な依存関係を含めましょう。

 # pyproject.toml [project] dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1" ]


次に、 virtualenv をアクティブにしてpip install .依存関係をインストールします。


それが完了したら、最後にテストをいくつか書いてみましょう。一般に、テストには、返されるモック化された応答、モック化された応答で戻り値を修正することによってテストしようとしている関数にパッチを適用し、最後に関数の呼び出しを含める必要があります。ユーザーのアクセス トークンを取得するサンプル テストは次のようになります。

 # tests/test_trelloservice.py # module imports from trellocli import SUCCESS from trellocli.trelloservice import TrelloService from trellocli.models import * # dependencies imports # misc imports def test_get_access_token(mocker): """Test to check success retrieval of user's access token""" mock_res = GetOAuthTokenResponse( token="test", token_secret="test", status_code=SUCCESS ) mocker.patch( "trellocli.trelloservice.TrelloService.get_user_oauth_token", return_value=mock_res ) trellojob = TrelloService() res = trellojob.get_user_oauth_token() assert res.status_code == SUCCESS


サンプル コードでは、 GetOAuthTokenResponse models.pyにまだ設定されていないモデルであることに注意してください。これは、よりクリーンなコードを作成するための構造を提供します。後でこれを実際に見ていきます。


テストを実行するには、 python -m pytestを実行するだけです。テストがどのように失敗するかに注目してください。しかし、それは問題ありません。最終的にはうまくいきます。


チャレンジ コーナー💡 自分でもっとテストを書いてみませんか?私のテストがどのようなものかを確認するには、このパッチを参照してください。


ここでは、 trelloserviceを構築しましょう。新しい依存関係、つまりpy-trelloラッパーを追加することから始めます。

 # pyproject.toml dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1", "py-trello==0.19.0" ]


もう一度、 pip install .依存関係をインストールします。

モデル

さて、モデルを構築することから始めましょう - trelloserviceで期待される応答を制御します。この部分については、単体テストとpy-trelloソース コードを参照して、予想される戻り値の種類を理解するのが最善です。


たとえば、ユーザーのアクセス トークンを取得したいとします。 py-trellocreate_oauth_token関数 ( ソース コード) を参照すると、戻り値は次のようなものになることがわかります。

 # trellocli/models.py # module imports # dependencies imports # misc imports from typing import NamedTuple class GetOAuthTokenResponse(NamedTuple): token: str token_secret: str status_code: int


一方で、競合する命名規則に注意してください。たとえば、 py-trelloモジュールにはListという名前のクラスがあります。この問題を回避するには、インポート時にエイリアスを指定します。

 # trellocli/models.py # dependencies imports from trello import List as Trellolist


この機会を自由に利用して、プログラムのニーズに合わせてモデルを調整してください。たとえば、戻り値から 1 つの属性のみが必要な場合、その値を全体として保存するのではなく、戻り値から抽出することを期待するようにモデルをリファクタリングできます。

 # trellocli/models.py class GetBoardName(NamedTuple): """Model to store board id Attributes id (str): Extracted board id from Board value type """ id: str


チャレンジ コーナー💡 自分でもっとモデルを書いてみませんか?私のモデルがどのように見えるかを確認するには、このパッチを参照してください。

ビジネスの論理

設定

モデルは終了です。正式にtrelloserviceのコーディングを開始しましょう。繰り返しますが、作成した単体テストを参照する必要があります。たとえば、現在のテストのリストではサービスを完全にカバーしていないため、必要に応じて常にテストを返し、テストを追加します。


通常どおり、すべての import ステートメントを先頭の方に含めます。次に、予想どおりTrelloServiceクラスとプレースホルダー メソッドを作成します。このアイデアは、 cli.pyでサービスの共有インスタンスを初期化し、それに応じてそのメソッドを呼び出すというものです。さらに、私たちはスケーラビリティを目指しているため、広範囲にわたるカバレッジが必要になります。

 # trellocli/trelloservice.py # module imports from trellocli import TRELLO_READ_ERROR, TRELLO_WRITE_ERROR, SUCCESS from trellocli.models import * # dependencies imports from trello import TrelloClient # misc imports class TrelloService: """Class to implement the business logic needed to interact with Trello""" def __init__(self) -> None: pass def get_user_oauth_token() -> GetOAuthTokenResponse: pass def get_all_boards() -> GetAllBoardsResponse: pass def get_board() -> GetBoardResponse: pass def get_all_lists() -> GetAllListsResponse: pass def get_list() -> GetListResponse: pass def get_all_labels() -> GetAllLabelsResponse: pass def get_label() -> GetLabelResponse: pass def add_card() -> AddCardResponse: pass


Ps は、今回テストを実行すると、テストがどのようにパスするかに注目してください。実際、これは私たちが正しい道を歩むことを確実にするのに役立ちます。ワークフローは、関数を拡張し、テストを実行し、合否を確認し、それに応じてリファクタリングする必要があります。

TrelloClient の認証と初期化

__init__関数から始めましょう。ここでget_user_oauth_token関数を呼び出し、 TrelloClientを初期化するという考え方です。繰り返しになりますが、このような機密情報は.envファイルにのみ保存する必要があることを強調し、 python-dotenv依存関係を使用して機密情報を取得します。 pyproject.tomlファイルをそれに応じて変更した後、認証手順の実装を開始しましょう。

 # trellocli/trelloservice.py class TrelloService: """Class to implement the business logic needed to interact with Trello""" def __init__(self) -> None: self.__load_oauth_token_env_var() self.__client = TrelloClient( api_key=os.getenv("TRELLO_API_KEY"), api_secret=os.getenv("TRELLO_API_SECRET"), token=os.getenv("TRELLO_OAUTH_TOKEN") ) def __load_oauth_token_env_var(self) -> None: """Private method to store user's oauth token as an environment variable""" load_dotenv() if not os.getenv("TRELLO_OAUTH_TOKEN"): res = self.get_user_oauth_token() if res.status_code == SUCCESS: dotenv_path = find_dotenv() set_key( dotenv_path=dotenv_path, key_to_set="TRELLO_OAUTH_TOKEN", value_to_set=res.token ) else: print("User denied access.") self.__load_oauth_token_env_var() def get_user_oauth_token(self) -> GetOAuthTokenResponse: """Helper method to retrieve user's oauth token Returns GetOAuthTokenResponse: user's oauth token """ try: res = create_oauth_token() return GetOAuthTokenResponse( token=res["oauth_token"], token_secret=res["oauth_token_secret"], status_code=SUCCESS ) except: return GetOAuthTokenResponse( token="", token_secret="", status_code=TRELLO_AUTHORIZATION_ERROR )


この実装では、予見可能なエラー (たとえば、ユーザーが認証中にDenyをクリックしたとき) を処理するためのヘルパー メソッドを作成しました。さらに、有効な応答が返されるまで再帰的にユーザーの承認を求めるように設定されています。実際には、ユーザーがアプリにアカウント データへのアクセスを承認しない限り、続行できないからです。


チャレンジ コーナー💡 TRELLO_AUTHORIZATION_ERRORに注意してください ?このエラーをパッケージ定数として宣言できますか?詳細については、「セットアップ」を参照してください。

ヘルパー関数

認証部分が完了したので、ユーザーの Trello ボードを取得することから始めて、ヘルパー関数に進みましょう。

 # trellocli/trelloservice.py def get_all_boards(self) -> GetAllBoardsResponse: """Method to list all boards from user's account Returns GetAllBoardsResponse: array of user's trello boards """ try: res = self.__client.list_boards() return GetAllBoardsResponse( res=res, status_code=SUCCESS ) except: return GetAllBoardsResponse( res=[], status_code=TRELLO_READ_ERROR ) def get_board(self, board_id: str) -> GetBoardResponse: """Method to retrieve board Required Args board_id (str): board id Returns GetBoardResponse: trello board """ try: res = self.__client.get_board(board_id=board_id) return GetBoardResponse( res=res, status_code=SUCCESS ) except: return GetBoardResponse( res=None, status_code=TRELLO_READ_ERROR )


リスト (列) を取得するには、 py-trelloBoardクラスをチェックアウトする必要があります。つまり、 Board値タイプの新しいパラメーターを受け入れる必要があります。

 # trellocli/trelloservice.py def get_all_lists(self, board: Board) -> GetAllListsResponse: """Method to list all lists (columns) from the trello board Required Args board (Board): trello board Returns GetAllListsResponse: array of trello lists """ try: res = board.all_lists() return GetAllListsResponse( res=res, status_code=SUCCESS ) except: return GetAllListsResponse( res=[], status_code=TRELLO_READ_ERROR ) def get_list(self, board: Board, list_id: str) -> GetListResponse: """Method to retrieve list (column) from the trello board Required Args board (Board): trello board list_id (str): list id Returns GetListResponse: trello list """ try: res = board.get_list(list_id=list_id) return GetListResponse( res=res, status_code=SUCCESS ) except: return GetListResponse( res=None, status_code=TRELLO_READ_ERROR )


チャレンジコーナー💡 get_all_labels関数とget_label関数を自分で実装できますか? py-trelloBoardクラスを修正します。私の実装がどのようなものかを確認するには、このパッチを参照してください。

新規カード追加機能

最後になりましたが、私たちはこれまでずっと目指してきたこと、つまり新しいカードの追加をついに達成しました。ここでは、以前に宣言した関数のすべてを使用するわけではないことに注意してください。ヘルパー関数の目的は、スケーラビリティを向上させることです。

 # trellocli/trelloservice.py def add_card( self, col: Trellolist, name: str, desc: str = "", labels: List[Label] = [] ) -> AddCardResponse: """Method to add a new card to a list (column) on the trello board Required Args col (Trellolist): trello list name (str): card name Optional Args desc (str): card description labels (List[Label]): list of labels to be added to the card Returns AddCardResponse: newly-added card """ try: # create new card new_card = col.add_card(name=name) # add optional description if desc: new_card.set_description(description=desc) # add optional labels if labels: for label in labels: new_card.add_label(label=label) return AddCardResponse( res=new_card, status_code=SUCCESS ) except: return AddCardResponse( res=new_card, status_code=TRELLO_WRITE_ERROR )


🎉 これで完了です。必要に応じて README を更新し、コードを GitHub にプッシュすることを忘れないでください。


おめでとう!あなたは勝ちました。もう一度プレイしますか (y/N)?

まとめ

お付き合いいただきありがとうございます:) このチュートリアルを通じて、単体テストを作成するときにモックを実装すること、一貫性を保つためにモデルを構造化すること、ソース コードを読んで主要な機能を見つけること、サードパーティのラッパーを使用してビジネス ロジックを実装することを学習しました。


パート 2 に注目してください。そこでは、CLI プログラム自体の実装について詳しく説明します。


それまでの間、連絡を取り合いましょう👀


GitHub ソースコード: https://github.com/elainechan01/trellocli