woky
woky

Reputation: 4946

Python, fileinput and Click

I'm using Click for command line argument parsing, and I'd like to use Python's fileinput library in one sub-command, but it seems Click leaves sys.argv as it is:

test.py:

import fileinput
import click

@click.group()
@click.option('-x', is_flag=True)
def cli(x):
    pass

@cli.command()
@click.option('-y', is_flag=True)
def read(y):
    for f in fileinput.input():
        for line in f:
            print(line)

if __name__ == '__main__':
    cli()

Test:

% python3 test.py -x read -y
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    cli()
  File "/usr/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/usr/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "test.py", line 12, in read
    for f in fileinput.input():
  File "/usr/lib/python3.8/fileinput.py", line 248, in __next__
    line = self._readline()
  File "/usr/lib/python3.8/fileinput.py", line 366, in _readline
    self._file = open(self._filename, self._mode)
FileNotFoundError: [Errno 2] No such file or directory: '-x'

Now of course I'd need to add some Click decorator to allow for arbitrary number of trailing arguments, but even the simple case for when there are no remaining arguments and fileinput takes sys.stdin, doesn't work.

AFAIK Python's argparse properly augments sys.argv so it'd work without problems. Can Click do that as well? How?


Note that I realize that it's probably possible to do what fileinput does with Click's decorators, and I can and will read about it in documentation. What I didn't find in the documentation and what I'm asking here is how to make Click pop arguments out of sys.argv like argparse does.

Upvotes: 3

Views: 495

Answers (1)

Hai Vu
Hai Vu

Reputation: 40763

I'm late answering this question, but better late than never. Woky is correct when suggesting adding arbitrary arguments.

import fileinput
import click

@click.group()
@click.option('-x', is_flag=True)
def cli(x):
    pass

@cli.command()
@click.argument("file", nargs=-1)
@click.option('-y', is_flag=True)
def read(file, y):
    for line in fileinput.input(file):
        print(line, end='')

if __name__ == '__main__':
    cli()

Notes

  1. I am adding an argument named 'file' and it takes arbitrary number of file names. If none was supplied, file == ()
  2. Note that when file == (), fileinput.input() knows to take input from stdin. This is different from file is None, in which case, fileinput.input() will take arguments from sys.argv
  3. No need for double for loop
  4. Since line contains the ending new line, I call print() with end='')

Upvotes: 0

Related Questions