SIOS Coati開発チームの清水です。
突然ですが、みなさんはファイル構成のコード化をどのようにおこなっていますか?
SIOS Coatiでは、環境の構築をAnsibleで行っています。Ansibleは非常に便利(どのくらい便利かは別の記事に譲ります)ですが、ちょっとしたひな形としてディレクトリやファイルを作るのには、少し大袈裟になりがちです。
たとえば個人的に、Pythonで開発をはじめるときは下のような構成が欲しかったりします。
1 2 3 4 5 6 7 8 9 10 11 |
--- - docs/ - src: - module: - __init__.py - main.py - tests: - test_main.py - README - requirements.txt - .gitignore |
このようにYAMLだとディレクトリの関係が綺麗に表現できます。
ならばいっそ、このYAMLからディレクトリ・ファイルをファイル構成を自動展開するスクリプトがあったらいいなと思ったので、作成しました。
普段からよく使う構成をYAMLにしておけば、ディレクトリ構成をドキュメント化しておく必要もありませんし、チーム内でひな形をお手軽に共有することもできます。
スクリプトと使い方を本記事の末尾に記載しますので、興味があるかたはぜひお試しください(スクリプトのご利用はあくまで自己責任でお願いいたします)。
なお実行にはPython3とPyYAMLライブラリが必要となるため、あらかじめインストールしてください。
ソースコード
template.py を作成し、以下のコードをコピー&ペーストしてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
#!python3 import sys from pathlib import Path import argparse import yaml class ItemMaker: def make_dirs(self, path): if path.exists(): raise FileExistsError('Directory already exists: ' + str(path)) path.mkdir(parents=True) def make_file(self, path): if path.exists(): raise FileExistsError('File already exists: ' + str(path)) if not path.parent.exists(): self.make_dirs(path.parent) path.touch() class DryRunMaker: def make_dirs(self, path): if path.exists(): raise FileExistsError('Directory already exists: ' + str(path)) def make_file(self, path): if path.exists(): raise FileExistsError('File already exists: ' + str(path)) class Builder: def __init__(self, outdir_path, maker, is_console=False): self.outdir_path = outdir_path self.maker = maker self.is_console = is_console def make(self, config_tree): self._make(self.outdir_path, config_tree) def _make(self, parent_path, tree): if not tree: return elif isinstance(tree, str): self._make_item(parent_path, tree) elif isinstance(tree, list): for item in tree: self._make(parent_path, item) elif isinstance(tree, dict): for next_dir, next_tree in tree.items(): self._make(parent_path / next_dir, next_tree) def _make_item(self, parent_path, item): assert isinstance(item, str), item item_path = parent_path / item if item.endswith('/'): self.maker.make_dirs(item_path) else: self.maker.make_file(item_path) if self.is_console: print('ok: ' + str(item_path)) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-d', '--dry-run', dest="dryrun", action="store_true", help="Enable dry-run") parser.add_argument('file', type=Path, help="Specify template file") parser.add_argument('-o', '--out', dest="out", type=Path, default=Path('.'), help="Specify root directory") args = parser.parse_args() maker = DryRunMaker() if args.dryrun else ItemMaker() builder = Builder(args.out, maker, True) if not args.file.exists(): print('Error: Not found template file: ' + str(args.file), file=sys.stderr) sys.exit(-1) with open(args.file.as_posix(), 'r') as f: config_tree = yaml.load(f) try: builder.make(config_tree) except Exception as e: print('Error: ' + str(e), file=sys.stderr) sys.exit(-1) |
コマンドのフォーマット
1 |
python3 template.py [-o out] [-d] file |
サンプルコマンド
1 |
python3 template.py -o out config.yml |
config.ymlの構成に基づいて、新しいファイル・ディレクトリを ./out ディレクトリ上に展開します。
なお、-o オプションを省略した場合は、カレントディレクトリ上に展開します。
もし、既にファイルが存在している場合は、その時点でプログラムは停止します(既存のファイルを変更することはありません)。
作成前にファイルの存在確認をする(Dry Run)場合は -d オプションを付与してください。
シンプルなコマンドですが、使いどころはあると思います。ぜひ使ってみてください!