LoginSignup
1
0

More than 3 years have passed since last update.

重複する行を順序を保ったまま整理したい

Last updated at Posted at 2021-01-22

経緯

.gitignore が秘伝のタレ状態になると、対象が重複することがある。

### C ###
# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp


### C++ ###
# Prerequisites
*.d  # <- 重複

# Compiled Object files
*.slo
*.lo
*.o  # <- 重複
*.obj  # <- 重複

# Precompiled Headers
*.gch
*.pch

# Linker files
*.ilk  # <- 重複

このせいで修正したつもりが「あれ、 .gitignore が反映されないな」みたいな時間の無駄が発生する。
ググると、「ソートしてから (^.*$)(\n(^\1$)){1,} で検索!」みたいな情報が出てくるんだが、
そうやって消すと順序が破壊されて「この *.map って何のために無視してるんだっけ?」が起きる。

面倒だけど、スクリプトにしておく。

実装

コピペしてさっと動かしたいので、Python3.6の標準ライブラリだけで。

arrange-duplicate-lines.py
#!/usr/bin/env python3

from argparse import ArgumentParser
from logging import basicConfig, getLogger, DEBUG, INFO
from pathlib import Path
from re import compile, escape, match

basicConfig(level=INFO)
logger = getLogger(__name__)


def main():
    parser = ArgumentParser(description='Remove or comment out duplicate lines, keeping them in order.')
    parser.add_argument('path', type=Path, help='Input file path.')
    delete_or_comment = parser.add_mutually_exclusive_group()
    delete_or_comment.add_argument('--delete', '-d', action='store_true', help='Flag to remove duplicate lines.')
    delete_or_comment.add_argument('--comment', '-c', type=str, default='#',
                                   help='The character to comment out duplicate lines.')
    parser.add_argument('--output', '-o', type=Path, default=None, help='Path to save.')
    args = parser.parse_args()

    result = []
    with open(args.path, mode='r', encoding='utf_8') as f:
        pattern_commented = compile(r'^{0}.*$'.format(escape(args.comment)))
        pattern_blank = compile(r'^\s+$')
        for i, line in enumerate(f):
            if match(pattern_commented, line) or match(pattern_blank, line):
                logger.debug(f'L{i:04}: [ AS IS  ] {line}')
                result.append(line)
            else:
                if line not in result:
                    logger.debug(f'L{i:04}: [  HIT   ] {line}')
                    result.append(line)
                else:
                    if not args.delete:
                        logger.debug(f'L{i:04}: [COMMENT] {line}')
                        result.append(f'{args.comment} {line}')
                    else:
                        logger.debug(f'L{i:04}: [ DELETE ] {line}')

    logger.debug('\n'.join(result))

    if args.output:
        with open(args.output, mode='w') as f:
            f.writelines(result)
        logger.info(f'Save: {args.output}')
    logger.info('Completed!')


if __name__ == "__main__":
    main()

せっかくなので、空行と元々コメントされているところはそのまま残し、重複行を削除するかコメントアウトするか選べるようにした。

結果

$ python3 arrange-duplicate-lines.py .gitignore -d -o .gitignore.delete
gitignore.delte
### C ###
# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp


### C++ ###
# Prerequisites

# Compiled Object files
*.slo
*.lo

# Precompiled Headers
*.gch
*.pch

# Linker files

$ python3 arrange-duplicate-lines.py .gitignore -c '###' -o .gitignore.comment
gitignore.comment
### C ###
# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp


### C++ ###
### # Prerequisites
### *.d

# Compiled Object files
*.slo
*.lo
### *.o
### *.obj

# Precompiled Headers
*.gch
*.pch

# Linker files
### *.ilk

愚直、ゆえに、もし *.txtlog-*.txt みたいな意味的な重複は残してしまうが、まぁ良しとしよう。
gitignore.io の名誉のために付記しておくと、
https://www.toptal.com/developers/gitignore/api/c,c++ のように一緒に指定した場合は上記のように重複が削除された状態で生成されるのを確認しました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0