動機
以前「比較的小規模で可搬的なPython toolのスケルトン」というのを作成した。
python
スクリプトの実行時にはあらかじめ作成しておいた対応する名前のラッパースクリプトを叩くだけでよいという利点があり、virtualenv
,venv
のように実行時にもシェルでsource
したり、activate
,deactivate
するのとは違う操作感が気に入って多用していた。
ただ多用しているうちにpython
スクリプトを書く段階での準備手順、(スケルトンディレクトリをコピー --> ファイル名変更 --> READMEファイルのcontents欄の修正 --> テストのためのpipでのモジュールインストール ... )が、案外めんどくさく感じられるようになってきた。この辺をさらに楽にするためにスクリプトを修正することにした。
実装方針 (お急ぎの方は次節へ)
- なによりも可搬性を最重要視し、1つのスクリプトファイルにする。その分、可読性(=メンテナンス性)が犠牲になってしまうのは甘受。
- 「比較的小規模で可搬的なPython toolのスケルトン」をベースにし、想定するディレクトリ構成を生成できる機能をもたせる。
- スクリプト自身は、作成したディレクトリ構成の下にコピーされ、以後コピーされたスクリプトは
python
スクリプトを実行するラッパースクリプトとして働く。 - Pythonスクリプトのテンプレートと、適切な名前でラッパースクリプトへのシンボリックリンクを作成する。
- それっぽい
Readme.md
ファイルも準備する。 - 必要に応じて、pipを用いて
python
モジュールを想定ディレクトリ構成の下にローカルにインストールする。 - python2とpython3を使い分けられるようにするが、基本python3をデフォルトにする。
ファイル置き場
依存関係 (Requirements)
- Python
- grep
- sed
- awk
- (optional) GNU realpath (On the macOS, it is assumed that it is installed as
grealpath
likemacports
and so on.)
Tested at macOS Big Sur (11.7.2)
使用法
実行スクリプトは、3つの動作モードがある。
1. 初期設定モード (init-mode)
ファイルの実体(runpyscr
)を、第一引数に--manage
オプションをつけて呼び、サブコマンドとしてinit
を指定する。最も単純な実行例としては、下記のように/somewhere/pyworktool
以下にbin
,lib
のディレクトリを作る。bin
の下に自分自身をコピーを作成し、lib
以下にコマンドラインで指定した名前(例ではpywork
)を元にpython
スクリプトテンプレート(pywork3.py
)を置き、それに対応してbin
以下にシンボリックリンク(pywork3
)を設置する。-3
は明示的にPhython3を想定するオプション。
% ./runpyscr --manage -p /somewhere/pyworktool -3 init pywork
% (cd /somewhere/ ; find pyworktool -ls )
drwxr-xr-x ...... pyworktool
drwxr-xr-x ...... pyworktool/bin
-rwxr-xr-x ...... pyworktool/bin/runpyscr
lrwxr-xr-x ...... pyworktool/bin/mng_pyenv -> runpyscr
lrwxr-xr-x ...... pyworktool/bin/mng_pyenv2 -> runpyscr
lrwxr-xr-x ...... pyworktool/bin/mng_pyenv3 -> runpyscr
lrwxr-xr-x ...... pyworktool/bin/pywork -> runpyscr
lrwxr-xr-x ...... pyworktool/bin/pywork3 -> runpyscr
drwxr-xr-x ...... pyworktool/lib
drwxr-xr-x ...... pyworktool/lib/python
drwxr-xr-x ...... pyworktool/lib/python/site-packages
-rw-r--r-- ...... pyworktool/lib/python/pywork3.py
lrwxr-xr-x ...... pyworktool/lib/python/pywork.py -> pywork3.py
初期設定モードで、-r
オプションを追加するとそれっぽいREADME.md
を、-g
をつけるとそれっぽい.gitignore
とかGit関連のファイルも設置する。
2. 管理モード(manage-mode)
初期モードを実行すると、mng_pyenv{,2,3}
というシンボリックリンクができている。このシンボリックでスクリプトが実行されると、runpyscr --manage
と実行されたときとおなじ動作になる。(mng_pyenv2
はrunpyscr --manage -2
,mng_pyenv3
はrunpyscr --manage -3
と同等。)
このモードで、サブコマンドinstall
とともに必要なpython module名を指定すると、pip
によりpython moduleがローカルにインストールされる。
% /somewhere/pyworktool/bin/mng_pyenv3 install pytz tzlocal
Collecting install
Downloading install-1.3.5-py3-none-any.whl (3.2 kB)
Collecting pytz
Using cached pytz-2022.7.1-py2.py3-none-any.whl (499 kB)
Collecting tzlocal
Using cached tzlocal-4.2-py3-none-any.whl (19 kB)
.....
Installing collected packages: install, pytz, tzdata, backports.zoneinfo, pytz-deprecation-shim, tzlocal
Successfully installed backports.zoneinfo-0.2.1 install-1.3.5 pytz-2022.7.1 pytz-deprecation-shim-0.1.0.post0 tzdata-2022.7 tzlocal-4.2
% ls /somewhere/pyworktool/lib/python/site-packages/3.8/
backports install pytz pytz_deprecation_shim tzdata tzlocal
backports.zoneinfo-0.2.1.dist-info install-1.3.5.dist-info pytz-2022.7.1.dist-info pytz_deprecation_shim-0.1.0.post0.dist-info tzdata-2022.7.dist-info tzlocal-4.2.dist-info
3. 実行モード(run-mode)
スクリプトがmng_pyenv{,2,3}
以外の名前のシンボリックリンクから実行された場合には、lib/python/シンボリックリンク名.py
というファイルに環境変数PYTHONPATH
を適切に設定して実行する。また、runpyscr
の第一引数が--manage
以外の場合、lib/python/{第一引数}.py
というファイルか、{第一引数}
というファイルを探して、第二引数以降のコマンドライン引数そのまま渡してを スクリプトファイルを実行する。下記の実行例では3つとも同じスクリプトが動作する。
% ~/tmp/pyworktool/bin/pywork3 Japan
Hello, Japan!
Python : 3.8.9 (/Applications/Xcode.app/Contents/Developer/usr/bin/python3)
....
% /somewhere/pyworktool/bin/runpyscr pywork3 Japan
Hello, Japan!
Python : 3.8.9 (/Applications/Xcode.app/Contents/Developer/usr/bin/python3)
....
% /somewhere/pyworktool/bin/runpyscr /somewhere/pyworktool/lib/python/pywork3.py Japan
Hello, Japan!
Python : 3.8.9 (/Applications/Xcode.app/Contents/Developer/usr/bin/python3)
....
追加機能
1.初期モードでのやりたいことを一辺に
初期モードでは、-m
オプションとともにmodule名を指定すると、pip
によるモジュールインストールも一気に行える。(複数指定可能)。また、-r
オプションとともに-t
オプションで作成するREAME.md
ファイルのタイトル部分も指定できる。また、作成するpythonのテンプレートも複数指定できる。
% ./runpyscr --manage -p /somewhere/pyworktool -g -r -t 'Worktool by Python' -3 -m pytz -m tzlocal init prepwork mainwork
Collecting pytz
Using cached pytz-2022.7.1-py2.py3-none-any.whl (499 kB)
Collecting tzlocal
Using cached tzlocal-4.2-py3-none-any.whl (19 kB)
......
Installing collected packages: pytz, tzdata, backports.zoneinfo, pytz-deprecation-shim, tzlocal
Successfully installed backports.zoneinfo-0.2.1 pytz-2022.7.1 pytz-deprecation-shim-0.1.0.post0 tzdata-2022.7 tzlocal-4.2
% ( cd /somewhere ; find pyworktool -ls )
...... pyworktool
...... pyworktool/README.md
...... pyworktool/.gitignore
...... pyworktool/bin
...... pyworktool/bin/runpyscr
...... pyworktool/bin/mng_pyenv -> runpyscr
...... pyworktool/bin/mng_pyenv2 -> runpyscr
...... pyworktool/bin/mng_pyenv3 -> runpyscr
...... pyworktool/bin/prepwork -> runpyscr
...... pyworktool/bin/prepwork3 -> runpyscr
...... pyworktool/bin/mainwork -> runpyscr
...... pyworktool/bin/mainwork3 -> runpyscr
...... pyworktool/lib
...... pyworktool/lib/python
...... pyworktool/lib/python/mainwork.py -> mainwork3.py
...... pyworktool/lib/python/mainwork3.py
...... pyworktool/lib/python/prepwork.py -> prepwork3.py
...... pyworktool/lib/python/prepwork3.py
...... pyworktool/lib/python/site-packages
...... pyworktool/lib/python/site-packages/.gitkeep
...... pyworktool/lib/python/site-packages/3.8
...... pyworktool/lib/python/site-packages/3.8/tzlocal-4.2.dist-info
......
また、README.md
の中身もコマンドライン引数で指定したタイトルから始まってそれっぽくなっているはず。
% cat /somewhere/pyworktool/README.md
#
# Worktool by Python
#
Skeleton for small portable tools by python script
- Contents:
1. README.md: This file
2. bin/mng_pyenv: Symblic link to 'runpyscr' for installing Python modules by pip locally.
2a. bin/mng_pyenv2: Same as above using pip2 as default
2b. bin/mng_pyenv3: Same as above using pip3 as default
3. bin/runpyscr: Wrapper bash script to invoke Python script. (Entity)
4. lib/python/site-packages: Directory where python modules are stored
5. lib/python/prepwork.py: Example Python script that use modules
5a. lib/python/prepwork3.py: Same as above using python3 as default
6. bin/prepwork: Symbolic link to 'runpyscr' to invoke prepwork.py.
6a. bin/prepwork3: Same as above using python3 as default
7. lib/python/mainwork.py: Example Python script that use modules
7a. lib/python/mainwork3.py: Same as above using python3 as default
8. bin/mainwork: Symbolic link to 'runpyscr' to invoke mainwork.py.
8a. bin/mainwork3: Same as above using python3 as default
9. .gitignore: Git-related file
10. lib/python/site-packages/.gitkeep: Git-related file to keep modules directory in repository.
2. テンプレートの追加
管理モードでサブコマンドadd
を使うと、スクリプトのテンプレートを追加することができる。
% /somewhere/pyworktool/bin/mng_pyenv -r add postwork
% ( cd /somewhere ; find pyworktool -ls ) ......
...... pyworktool/bin/postwork3 -> runpyscr
...... pyworktool/bin/postwork -> runpyscr
......
...... pyworktool/lib/python/postwork3.py
...... pyworktool/lib/python/postwork.py -> postwork3.py
......
新しいpythonスクリプトのテンプレートとその実行のためのrunpyscr
へのシンボリックリンクが作成される。それに加えて、下記のようにREADME.md
にも記述が追記される。
% cat /somewhere/pyworktool/README.md
......
11. lib/python/postwork.py: Example Python script that use modules
11a. lib/python/postwork3.py: Same as above using python3 as default
12. bin/postwork: Symbolic link to 'runpyscr' to invoke postwork.py.
12a. bin/postwork3: Same as above using python3 as default
......
応用
Pythonテンプレートの内容の変更すれば...
runpyscr
は、自分自身の
########## __py_template_start__ ##########
と書かれた行と
########## __py_template_end__ ##########
と書かれた行で挟まれた部分(#
の数は1個以上あればいくつでもよい)をsed
で抽出し、
## __py_shebang_pattern__ ##
の行を、コマンドライン引数、もしくは環境変数PYTHON
、もしくは環境変数PATH
の中で見つけたpython
に応じてシェバンに置き換えて作成している。なので、runpyscr
のこの部分を書き換えることで、作成されるpythonテンプレートの内容を変えることができる。
Python script の持ち歩き/配布
さらに別の使い方として、ある環境で作成したpythonスクリプトを他の環境に持っていって使ったり、他の人に使ってもらう際にファイルを配布するために使うことができる。前述の通り、このスクリプトはファイルの実体が直接実行されるか、シンボリックで別名で実行されるかで動作を変えているが、runpyscr
というファイル名そのものは動作モードの判定に使っていないので、適宜別名に変えてもよい。たとえば、hostA
でよく使っているpythonスクリプトtool.py
を他の環境HostB
に持っていくときに、
UserA@HostA % cp -ai 'runpyscr' '/somewhere/tool-dist'
UserA@HostA % emacs /somewhere/tool-dist # py_templete の部分をtool.pyの内容で書き換える。
UserA@HostA % scp /somewhere/tool-dist UserB@HostB:/tmp/tool-dist
UserB@HostB % /tmp/tool-dist --manage -p /destination/util -g -r -t 'Tool by UserA@HostA' -3 -m pytz -m tzlocal -m other-python-module init tool
UserB@HostB % /destination/util/bin/tool # 実行可能
といった方法が使える。HostB
でpip
でモジュールをインストールする権限がない場合にとくに有用。頑張れば、tool-dist
の該当部分をtool.py
を置き換えるのも、sed
で自動化(スクリプト化)することもできそう。
Pythonのテンプレート部分だけでなく、.gitignore
やREADME.md
もrunpyscr
のテンプレート部を書き換えればカスタマイズできる。ただし、sedでいくつかキーワード置換している点には注意が必要かもしれない。
複数のPythonスクリプトの持ち歩き/配布
Pythonスクリプトのテンプレートには名前をつけることも可能である。
もし、init
サブコマンドやadd
コマンドの引数で与えられたスクリプト名が{scriptname}
だった時に、もし自分自身のファイルに
########## __{scriptname}_template_start__ ##########
と書かれた行と
########## __{scriptname}_template_end__ ##########
で挟まれた行(#
の数は1個以上あればいくつでもよい)がある場合には、この間の行がPythonスクリプトのテンプレートとして使われる。
この機能により複数のpythonスクリプトをセットで配布することが可能になる。
UserA@HostA % cp -ai 'runpyscr' '/somewhere/tool-dist'
UserA@HostA % emacs /somewhere/tool-dist # py_templeteの末尾に下記を追記する。
UserA@HostA % cat /somewhere/tool-dist
....
########## __scriptA_template_start__ ##########
## __py_shebang_pattern__ ##
# -*- coding: utf-8 -*-
... scriptAの内容
########## __scriptA_template_end__ ##########
########## __scriptB_template_start__ ##########
## __py_shebang_pattern__ ##
# -*- coding: utf-8 -*-
... ScriptBの内容
########## __scriptB_template_end__ ##########UserA@HostA % scp /somewhere/tool-dist UserB@HostB:/tmp/tool-dist
UserB@HostB % /tmp/tool-dist --manage -p /destination/util -g -r -t 'Tool by UserA@HostA' -3 -m pytz -m tzlocal init scriptA scriptB
UserB@HostB % /destination/util/bin/scriptA # 実行可能
UserB@HostB % /destination/util/bin/scriptB # 実行可能
おまけ (コマンドラインヘルプ)
% ./runpyscr --manage -h
[Usage] % mng_pyenv{,2,3} [options] sub-command [arguments]
% runpyscr --manage [options] sub-command [arguments]
[sub-commands]
% runpyscr --manage [options] init [scriptnames] ... setup directory tree,
and prepare templates if arguments are given.
% runpyscr --manage [options] add script_names ... prepare python script templates.
% runpyscr --manage install module_names ... Install python module with pip locally.
% runpyscr --manage download module_names ... Download python module with pip locally.
% runpyscr --manage clean ... Delete local python module for corresponding python version.
% runpyscr --manage distclean/cleanall/allclean ... Delete local python module for all python version.
% runpyscr --manage info ... Show information.
[Options]
-h : Show this message
-P arg : specify python command / path
-v : Show verbose messages
-q : Supress verbose messages (default)
-n : Dry-run mode.
-2 : Prioritize Python2 than Python3
-3 : Prioritize Python3 than Python2
[Options for sub-command: setup/init ]
-p : prefix of the directory tree. (Default: Grandparent directory if the name of parent directory of
runpyscr is bin, otherwise current working directory.)
-M : moving this script body into instead of copying
-g : setup files for git
-G : do not setup files for git (default)
-r : setup/update README.md
-R : do not setup/update README.md (default)
-k : keep backup file of README.md when it is updated.
-K : do not keep backup file of README.md when it is updated. (default)
-t arg : Project title
[Options for sub-command: install/download ]
-i arg : specify pip command