Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docopt.__doc__ docstring #81

Closed
keleshev opened this issue Jan 20, 2013 · 15 comments
Closed

docopt.__doc__ docstring #81

keleshev opened this issue Jan 20, 2013 · 15 comments

Comments

@keleshev
Copy link
Member

Does anyone think it's a good I dea to read docopt.__doc__ from README.rst file? I can't see people doing similar things, there should be a reason.

I'm thinking of adding the following to docopt.py:

try:
    import os
    docopt.__doc__ = open(os.path.join(
        os.path.dirname(os.path.abspath(__file__)), 'README.rst' )).read()
except:
    pass
@keleshev
Copy link
Member Author

It is extremely silly that docopt doesn't have docstrings. Also see #76. However I'm even more afraid of maintaining duplicate documentation:

  • www.docopt.org (somewhat different scope of documentation)
  • docopt.readthedocs.org (actually, outdated; should it be deprecated, or should it use README.rst?)
  • github (uses README.rst)
  • pypi (will use README.rst from 0.6.0)
  • docstrings (maybe should also use README.rst?

@ghost
Copy link

ghost commented Jan 21, 2013

It's a different scope of documentation again, I think. A module docstring's purpose is to provide a brief overview of the module and its functions, not tutorials and installation instructions. Besides, it'd break docopt.py's self-sufficiency.

For #76, ordinary hash comments at the beginning of the file should do.

@keleshev
Copy link
Member Author

Added the following:

b339ca9

Does anyone (@docopt) think there's something missing or could be improved (considering this should be a brief documentation)?

@kmike
Copy link

kmike commented Feb 7, 2013

There is a subtle issue with docopt.docopt doctring: it currently plays badly with doctests.

This is not an issue for docopt itself because it doesn't use doctests. But if a project which uses doctests decides to embed docopt.py into the distribution (instead of requiring users to install it from pypi), then doctests for this project may start to fail because "vendorized" docopt is not available as "from docopt import docopt" (esp. under Python 3 where relative imports must be explicit).

So I think the docstring for docopt.docopt shouldn't use "from docopt import docopt" because this is incorrect when docopt is "vendorized", and "vendorizing" is suggested in README ("Alternatively, you can just drop docopt.py file into your project--it is self-contained.")

Maybe just remove the example?

@kmike
Copy link

kmike commented Feb 7, 2013

The example is not a valid doctest anyway because

    >>> doc = '''
    Usage:
        my_program tcp <host> <port> [--timeout=<seconds>]
        my_program serial <port> [--baud=<n>] [--timeout=<seconds>]
        my_program (-h | --help | --version)

    Options:
        -h, --help  Show this screen and exit.
        --baud=<n>  Baudrate [default: 9600]
    '''

is not a valid doctest syntax (... should be used for indentation).

If you want to keep the example, then this would make it valid doctest even if vendorized:

    Example
    -------
    >>> from docopt import docopt # doctest: +SKIP
    >>> doc = '''
    ...    Usage:
    ...        my_program tcp <host> <port> [--timeout=<seconds>]
    ...        my_program serial <port> [--baud=<n>] [--timeout=<seconds>]
    ...        my_program (-h | --help | --version)
    ...
    ...    Options:
    ...        -h, --help  Show this screen and exit.
    ...        --baud=<n>  Baudrate [default: 9600]
    ...    '''
    >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30']
    >>> docopt(doc, argv)
    {'--baud': '9600',
     '--help': False,
     '--timeout': '30',
     '--version': False,
     '<host>': '127.0.0.1',
     '<port>': '80',
     'serial': False,
     'tcp': True}

@keleshev
Copy link
Member Author

keleshev commented Feb 8, 2013

@kmike, thanks for pointing this out. I will integrate your patch later this day. However, I really dislike having the doctest flag (# doctest: +SKIP), which is ugly, I think. But I still want to keep the example. Cannot think of any solution right now.

@kmike
Copy link

kmike commented Feb 9, 2013

Yes, the doctest syntax is quite ugly, and we're lucky that there is only a single line that have to be skipped.

Alternatively, "import" part may be described in a free-text form, but this is also going to be quite ugly. The root issue here (IMHO) is that "docopt" is ambiguous: it can mean both module and function. I always (well, all 3 or 4 times) tend to do "import docopt", then copy-paste an example from README below the __main__ (arguments = docopt(__doc__, version='Naval Fate 2.0')), then it fails, go fix the import.

Imagine the function is named 'parse', not 'docopt'. Then the example in README would be

import docopt

if __name__ == '__main__':
    arguments = docopt.parse(__doc__, version='Naval Fate 2.0')
    print(arguments)

(note how it saves some keystrokes in this example; most probably, both "import" statement and "docopt.parse(..)" call would occur only once in a source code so this should save keystrokes in real projects as well).

With docopt.parse "import docopt" statement may be excluded from __doc__ example because the example in doctring can be interpreted unambiguously even without this import; if we want to describe this import in a free-form text (before the example) it also should be easier for "import docopt" than for "from docopt import docopt".

Don't get me wrong, I think that "docopt.docopt" vs "docopt.parse" thing is a minor question which is not very important, and that docopt is wonderful :)

@keleshev
Copy link
Member Author

keleshev commented Feb 9, 2013

@kmike, I understand you well. Too bad Python does not allow modules to be callable. I would imagine that if module had a __call__ function (like objects), then it could be callable with:

import docopt

args = docopt(__doc__)

I tracked down, why module cannot be callable, and here is the answer:

http://svn.python.org/projects/python/trunk/Objects/object.c
(search for "PyCallable_Check").

But it is so for no good reason apparently.

@kmike
Copy link

kmike commented Feb 9, 2013

I have a proven track record of abusing module subclasses (for overcoming fabric limitations, in https://github.com/kmike/fabric-taskset), so here is a hack using module subclass:

# docopt.py
# ...

class _DocoptModule(type(sys)):
    def __call__(self, doc, argv=None, help=True, version=None, options_first=False):
        return docopt(doc, argv, help, version, options_first)

    def __getattribute__(self, item):
        if item == '__call__':
            return object.__getattribute__(self, '__call__')
        return getattr(_original_module, item)

_original_module = sys.modules[__name__]
sys.modules[__name__] = _DocoptModule(__name__)

UPD: getattr -> getattribute

keleshev added a commit that referenced this issue Feb 10, 2013
@keleshev
Copy link
Member Author

@kmike, I didn't know that is possible. Thanks for suggestion. I made a call-module branch with support for that (see the commit referenced above).

I'm still not sure if it is a good idea or not. @docopt/docopt anyone?

On one hand it avoids the import docopt vs from docopt import docopt confusion. On the other hand it's not something common in Python. Also it sort of violates the TOOWTDI.

@kmike
Copy link

kmike commented Feb 10, 2013

@halst, just for the record: I think that docopt.parse() is better than docopt() and docopt.docopt(), but docopt.parse() has a drawback of being inconsistent with non-Python docopts(?); as for docopt.docopt() vs docopt() - no ideas.

@ghost
Copy link

ghost commented Feb 11, 2013

I'd really rather not clutter docopt.py up with an ugly hack just to offer some syntactic sugar. AFAIK it's pretty common in the Python world for modules that really only export one function or class to just share their name with the function/class, and I think we should stick with that, confusing as it may be. If it's really a problem, docopt.parse() is the better solution.

@keleshev
Copy link
Member Author

keleshev commented Mar 7, 2013

Agree, callable module is an ugly hack. But what about using a callable module in order to give a better error-message? Along the lines of:

>>> import docopt
>>> args = docopt('usage: prog', '')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'module' object is not callable, use 'from docopt import docopt' instead

@ghost
Copy link

ghost commented Mar 7, 2013

I don't really like that either; it's an awful lot of weird code just to add one clause to the error message. (I don't like the Dict class for much the same reason, but that's another can of worms I suppose.) People not understanding what "'module' object is not callable" means isn't the problem anyway, it's that they forget to import the function proper the first time around.

@keleshev
Copy link
Member Author

keleshev commented Apr 8, 2013

Agree, callable module is a stupid idea (at least in Python). Docstring is now doctestable, and I set tox/travis to doctest it only on 2.7, which tests validity of documentation without needing to deal with Python 2 vs 3 differences.

@keleshev keleshev closed this as completed Apr 8, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants