フォルダごとに階層化されていたファイルをフラットにしながらコピーする

# 概要
- フォルダごとに階層化されていたファイルをフラットにしながらコピーします。
- shutilとかいろいろ探したのですが、見つからなかったのでちょいプロ(ちょっとしたプログラム)つくりました

# 基本動作

```shell-session:フォルダごとに階層化されていたファイル
└─test
   └─a
       ├─01
       │      1.xlsx
       │
       └─02
           ├─01
           │      2.xlsx
           │
           └─02
               └─01
                       03.txt
```

↓こうします。(test2/b以下にはフォルダ階層がない)

```shell-session:フラットに
├─test
│  └─a
│      ├─01
│      │      1.xlsx
│      │
│      └─02
│          ├─01
│          │      2.xlsx
│          │
│          └─02
│              └─01
│                      03.txt
│
└─test2
    └─b
            03.txt
            1.xlsx
            2.xlsx
```

# 使い方

```pycon
In [16]: import copy_flat

In [17]: copy_flat.copy_flat(r"test\a", r"test2\b")
3 files is to be copied.
    1/    3: ==> test2\b\1.xlsx
    2/    3: ==> test2\b\2.xlsx
    3/    3: ==> test2\b\03.txt
```

```pycon:すでに同名ファイルがあってコピーしないとき
In [24]: copy_flat.copy_flat(r"test\a", r"test2\b")
3 files is to be copied.
    1/    3: ignored. it already exists. test2\b\1.xlsx
    2/    3: ignored. it already exists. test2\b\2.xlsx
    3/    3: ignored. it already exists. test2\b\03.txt
```

- パスは相対パスでも絶対パスでもOKです。
- Windowsのフォルダによく作られてしまうThumb.dbは、一回コピーしたらスキップしたい・・・というイメージでつくってます。

# コード

```py:copy_flat.py
# -*- coding: utf-8 -*-
import os
import pathlib
import shutil

def copy_flat(src, dest):

    # コピー元ファイルリスト化しておく(if f.is_file()でフォルダを除外している)
    src = pathlib.Path(src)
    src = [f for f in src.glob("**/*") if f.is_file()]

    # コピー先のディレクトリがなければ作成しておく(os.makedirsで階層構造も一気に作成する)
    # 　　http://www.gesource.jp/programming/python/code/0009.html
    dest = pathlib.Path(dest)
    if not dest.is_dir():
        os.makedirs(dest)

    # コピー先ファイルリスト化しておく(コピー先のフォルダ名とコピー元の"ファイル名"をパス結合(/)しておく)
    #   https://docs.python.jp/3/library/pathlib.html
    #   11.1.2.2. 演算子
    dest = [ (dest / f.name) for f in src]

    print("{0} files is to be copied.".format(len(src)))

    # ファイルをコピーする(zipで複数のリストをまとめてループする)
    # http://python.civic-apps.com/zip-enumerate/s
    for i, (s, d) in enumerate(zip(src, dest)):
        print("{0:>5}/{1:>5}: ".format(i+1, len(src)), end="")
        if d.is_file():
            print("ignored. it already exists. {0}".format(d))
        else:
            # ファイルをコピー
            shutil.copy(s, d)
            print("==> {0}".format(d))
```


# 改造

- 複数のフォルダパスを入力として受け取れるようにした
    - コードからはリストとしてで受け取る
- コマンドラインで実行できるようにした
    - -iのパラメータで複数フォルダパスを指定できるようにした

(「フォルダかどうかはちゃんとチェックしてない」とかあるけど、これ以上やるとちょいプロというには手間をかけすぎになるのでやめる)

```pycon
In [59]: copy_flat.copy_flat(r"test\a", r"test2\b")
4 files is to be copied.
    1/    4: ==> test2\b\test.txt
・・・略・・・

In [60]: copy_flat.copy_flat([r"test\a",  r"test2\b"], r"test3\c\d")
8 files is to be copied.
    1/    8: ==> test3\c\d\test.txtt
・・・略・・・

In [61]: %run copy_flat.py -i test\a -o test4
4 files is to be copied.
    1/    4: ==> test4\test.txt
・・・略・・・
```


```py:copy_flat.py
# -*- coding: utf-8 -*-
import os
import pathlib
import shutil
from argparse import ArgumentParser

def copy_flat(src, dest):
    # コピー元ファイルリスト化しておく(if f.is_file()でフォルダを除外している)
    src = _make_file_list(src)

    # コピー先のディレクトリがなければ作成しておく(os.makedirsで階層構造も一気に作成する) - http://www.gesource.jp/programming/python/code/0009.html
    dest = _get_dest_folder(dest)
    if not dest.is_dir():
        os.makedirs(dest)

    # コピー先ファイルリスト化しておく(コピー先のフォルダ名とコピー元の"ファイル名"をパス結合(/)しておく) - https://docs.python.jp/3/library/pathlib.html - 11.1.2.2. 演算子
    dest = [ (dest / f.name) for f in src]

    print("{0} files is to be copied.".format(len(src)))

    # ファイルをコピーする(zipで複数のリストをまとめてループする) - http://python.civic-apps.com/zip-enumerate/
    for i, (s, d) in enumerate(zip(src, dest)):
        print("{0:>5}/{1:>5}: ".format(i+1, len(src)), end="")
        if d.is_file():
            print("ignored. it already exists. {0}".format(d))
        else:
            # ファイルをコピー
            shutil.copy(s, d)
            print("==> {0}".format(d))

def _set_argparser():
    # 【Python】コマンドライン引数を解析する(argparseの利用) - Flat Leon Work - http://flat-leon.hatenablog.com/entry/python_argparse
    parser = ArgumentParser(
    description = '''
    This script is used to copy the files in structured folders to a folder.(this script copies the files flatly)
    ''')

    # 16.4.3. add_argument() メソッド(原文)
    # ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])
    parser.add_argument(
        "-i",
        type=str,
        nargs = '+',
        required = True,
        help = '''
        The source folder paths to the structured folders which inlude some files.
        Absolute or relative path can be used.
        This parameter must include more than one(1) absolute or relative path.
        ''')

    parser.add_argument(
        "-o",
        type = str,
        nargs = 1,
        required = False,
        default = [os.getcwd()],
        help = '''
        The destination folder path will store the files.
        if not set, current working folder(os.getcwd()) is to be used.
        ''')

    return parser.parse_args()

def _make_file_list(srcs):
    file_list = []
    if isinstance(srcs, list):
        for s in srcs:
            file_list += [f for f in pathlib.Path(s).glob("**/*") if f.is_file()]
    elif isinstance(srcs, str):
        file_list += [f for f in pathlib.Path(srcs).glob("**/*") if f.is_file()]
    else:
        print("Error. unkown instance at _make_file_list.")
    return file_list


def _get_dest_folder(dest):
    dest_folder = ""
    if isinstance(dest, list):
        if len(dest) > 0:
            dest_folder = str(dest[0])
    elif isinstance(dest, str):
        dest_folder = dest
    else:
        print("Error. unkown instance at _get_dest_folder.")

    # https://support.microsoft.com/ja-jp/help/879749
    # スラッシュ (/)、円記号 (\)、不等号記号 (<>)、アスタリスク (*)、疑問符 (?)、ダブル クォーテーション (")、縦棒 (|)、コロン (:)、セミコロン (;) 
    # の置き換えをしたほうがいいかも
    return pathlib.Path(dest_folder)

if __name__ == '__main__':

    args = _set_argparser()

    copy_flat(args.i, args.o)

```



# 関係ないけど

ちょいプロ(ちょっとしたプログラム)って、[Rubyを256倍使うための本 邪道編](http://amzn.asia/3U5gEtm)とかで使われてて個人的に好きなんだけど、ネットだと[Borland C++ Builderでちょいプロ。](http://bsd.jugem.jp/?eid=11)くらいでしか使われてない。
別のはやりの言い方があるんだろうか。