From 369f7eb1df01cc46e0212aa131536e99c1fa68ba Mon Sep 17 00:00:00 2001 From: kotfu Date: Sat, 6 Jul 2019 12:51:55 -0600 Subject: Work on Getting Started section of documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - change the walk through from example.py to a new first_app.py (based on example.py) - remove the feature tour section, we’ll use the first application to demo the key features --- docs/examples/example.rst | 301 ----------------------------------------- docs/examples/first_app.rst | 303 ++++++++++++++++++++++++++++++++++++++++++ docs/examples/index.rst | 2 +- docs/overview/featuretour.rst | 5 - docs/overview/index.rst | 3 +- docs/overview/summary.rst | 4 +- examples/first_app.py | 61 +++++++++ 7 files changed, 367 insertions(+), 312 deletions(-) delete mode 100644 docs/examples/example.rst create mode 100644 docs/examples/first_app.rst delete mode 100644 docs/overview/featuretour.rst create mode 100755 examples/first_app.py diff --git a/docs/examples/example.rst b/docs/examples/example.rst deleted file mode 100644 index 58ae9cdb..00000000 --- a/docs/examples/example.rst +++ /dev/null @@ -1,301 +0,0 @@ -Simple Application -================== - -.. _cmd: https://docs.python.org/3/library/cmd.html - -Here's a quick walkthrough of a simple application which demonstrates 8 features of -``cmd2``: - -* Settings -* Commands -* Argument Parsing -* Generating Output -* Help -* Shortcuts -* Multiline Commands -* History - -If you don't want to type as we go, you can download the complete source for -this example app. - - -Basic Application ------------------ - -First we need to create a new ``cmd2`` application. Create a new file ``example.py`` with the following contents:: - - #!/usr/bin/env python - """ - A sample application for cmd2. - """ - import cmd2 - - - class CmdLineApp(cmd2.Cmd): - """Example cmd2 application. """ - - - if __name__ == '__main__': - import sys - c = CmdLineApp() - sys.exit(c.cmdloop()) - -We have a new class ``CmdLineApp`` which is a subclass of -:ref:`api/cmd:cmd2.Cmd`. When we tell python to run our file like this: - -.. code-block:: shell - - $ python example.py - -it creates an instance of our class, and calls the ``cmdloop()`` method. This -method accepts user input and runs commands based on that input. Because we -subclassed ``cmd2.Cmd``, our new app already has a bunch of features built in. - -Congratulations, you have a working ``cmd2`` app. You can run it, and then type -``quit`` to exit. - - -Create a New Setting --------------------- - -Before we create our first command, we are going to add a setting to this app. -``cmd2`` includes robust support for :ref:`features/settings:Settings`. You -configure settings during object initialization, so we need to add an -initializer to our class:: - - def __init__(self): - super().__init__() - - # Make maxrepeats settable at runtime - self.maxrepeats = 3 - self.settable['maxrepeats'] = 'max repetitions for speak command' - -In that initializer, the first thing to do is to make sure we initialize -``cmd2``. That's what the ``super().__init__()`` line does. Then we create an -attribute to hold our setting, and then add a description of our setting to the -``settable`` dictionary. If our attribute name isn't in ``settable``, then it -won't be treated as a setting. Now if you run the script, and enter the ``set`` -command to see the settings, like this: - -.. code-block:: shell - - $ python example.py - (Cmd) set - -you will see our ``maxrepeats`` setting show up with it's default value of ``3``. - - -Create A Command ----------------- - -Now we will create our first command, called ``speak`` which will echo back -whatever we tell it to say. We are going to use an :ref:`argument processor -` so the ``speak`` command can -shout and talk piglatin. We will also use some built in methods for -:ref:`generating output `. Add -this code to ``example.py``, so that the ``speak_parser`` attribute and the -``do_speak()`` method are part of the ``CmdLineApp()`` class:: - - speak_parser = argparse.ArgumentParser() - speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') - speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') - speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') - speak_parser.add_argument('words', nargs='+', help='words to say') - - @cmd2.with_argparser(speak_parser) - def do_speak(self, args): - """Repeats what you tell me to.""" - words = [] - for word in args.words: - if args.piglatin: - word = '%s%say' % (word[1:], word[0]) - if args.shout: - word = word.upper() - words.append(word) - repetitions = args.repeat or 1 - for _ in range(min(repetitions, self.maxrepeats)): - # .poutput handles newlines, and accommodates output redirection too - self.poutput(' '.join(words)) - -Up at the top of the script, you'll also need to add:: - - import argparse - -There's a bit to unpack here, so let's walk through it. We created -``speak_parser``, which uses the `argparse -`_ module from the Python -standard library to parse command line input from a user. There is nothing thus far -that is specific to ``cmd2``. - -There is also a new method called ``do_speak()``. In both cmd_ and ``cmd2``, -methods that start with ``do_`` become new commands, so by defining this method -we have created a command called ``speak``. - -Note the ``@cmd2.with_argparser`` decorator on the ``do_speak()`` method. This -decorator does 3 useful things for us: - -1. It tells ``cmd2`` to process all input for the ``speak`` command using the - argparser we defined. If the user input doesn't meet the requirements defined by - the argparser, then an error will be displayed for the user. -2. It alters our ``do_speak`` method so that instead of receiving the raw user - input as a parameter, we receive the namespace from the argparser. -3. It creates a help message for us based on the argparser. - -You can see in the body of the method how we use the namespace from the -argparser (passed in as the variable ``args``). We build an array of words which -we will output, honoring both the ``--piglatin`` and ``--shout`` options. - -At the end of the method, we use our ``maxrepeats`` setting as an upper limit to -the number of times we will print the output. - -The last thing you'll notice is that we used the ``self.poutput()`` method to -display our output. ``poutput()`` is a method provided by ``cmd2``, which I -strongly recommend you use anytime you want to :ref:`generate output `. It provides the following benefits: - -1. Allows the user to redirect output to a text file or pipe it to a shell process -2. Gracefully handles ``BrokenPipeWarning`` exceptions for redirected output -3. Makes the output show up in a :ref:`transcript ` -4. Honors the setting to strip embedded ansi sequences (typically used for background and foreground colors) - -Go run the script again, and try out the ``speak`` command. Try typing ``help speak``, and you -will see a lovely usage message describing the various options for the command. - -With those few lines of code, we created a :ref:`command -`, used an :ref:`Argument Processor `, added a nice :ref:`help message ` for our users, and :ref:`generated some output `. - - -Shortcuts ---------- - -``cmd2`` has several capabilities to simplify repetitive user input: :ref:`Shortcuts, Aliases, and Macros `. Let's add a shortcut to our application. Shortcuts are character strings that can be used instead of a command name. For example, ``cmd2`` has support for a shortcut ``!`` which runs the ``shell`` command. So instead of typing this: - -.. code-block:: shell - - (Cmd) shell ls -al - -you can type this: - -.. code-block:: shell - - (Cmd) !ls -al - -Let's add a shortcut for our ``speak`` command. Change the ``__init__()`` method so it looks like this:: - - def __init__(self): - shortcuts = cmd2.DEFAULT_SHORTCUTS - shortcuts.update({'&': 'speak'}) - super().__init__(shortcuts=shortcuts) - - # Make maxrepeats settable at runtime - self.maxrepeats = 3 - self.settable['maxrepeats'] = 'max repetitions for speak command' - -Shortcuts are passed to the ``cmd2`` initializer, and if you want the built-in -shortcuts of ``cmd2`` you have to pass them. These shortcuts are defined as a -dictionary, with the key being the shortcut, and the value containing the -command. When using the default shortcuts and also adding your own, it's a good -idea to use the ``.update()`` method to modify the dictionary. This way if you -add a shortcut that happens to already be in the default set, yours will -override, and you won't get any errors at runtime. - -Run your app again, and type: - -.. code-block:: shell - - (Cmd) shortcuts - -to see the list of all of the shortcuts, including the one for speak that we just created. - - -Multiline Commands ------------------- - -Some use cases benefit from the ability to have commands that span more than one -line. For example, you might want the ability for your user to type in a SQL -command, which can often span lines and which are terminated with a semicolon. -Let's add a :ref:`multiline command ` to our application. First we'll create a new command called -``orate``. This code shows both the definition of our ``speak`` command, and the -``orate`` command:: - - @cmd2.with_argparser(speak_parser) - def do_speak(self, args): - """Repeats what you tell me to.""" - words = [] - for word in args.words: - if args.piglatin: - word = '%s%say' % (word[1:], word[0]) - if args.shout: - word = word.upper() - words.append(word) - repetitions = args.repeat or 1 - for _ in range(min(repetitions, self.maxrepeats)): - # .poutput handles newlines, and accommodates output redirection too - self.poutput(' '.join(words)) - - # orate is a synonym for speak which takes multiline input - do_orate = do_speak - -With the new command created, we need to tell ``cmd2`` to treat that command as -a multi-line command. Modify the super initialization line to look like this:: - - super().__init__(multiline_commands=['orate'], shortcuts=shortcuts) - -Now when you run the example, you can type something like this: - -.. code-block:: shell - - (Cmd) orate O for a Muse of fire, that would ascend - > The brightest heaven of invention, - > A kingdom for a stage, princes to act - > And monarchs to behold the swelling scene! ; - -Notice the prompt changes to indicate that input is still ongoing. ``cmd2`` will -continue prompting for input until it sees an unquoted semicolon (the default -multi-line command termination character). - - -History -------- - -``cmd2`` tracks the history of the commands that users enter. As a developer, -you don't need to do anything to enable this functionality, you get it for free. -If you want the history of commands to persist between invocations of your -application, you'll need to do a little work. The -:ref:`features/history:Command History` page has all the details. - -Users can access command history using two methods: - -- the `readline `_ library which provides a python interface to the `GNU readline library `_. -- the ``history`` command which is built-in to ``cmd2`` - -From the prompt in a ``cmd2``-based application, you can press ``-p`` to -move to the previously entered command, and ``-n`` to move to the next -command. You can also search through the command history usint ``-r``. The -`GNU Readline User Manual -`_ has all the -details, including all the available commands, and instructions for customizing -the key bindings. - -The ``history`` command allows a user to view the command history, and select -commands from history by number, range, string search, or regular expression. -With the selected commands, users can: - -- re-run the commands -- edit the selected commands in a text editor, and run them after the text editor exits -- save the commands to a file -- run the commands, saving both the commands and their output to a file - -Learn more about the ``history`` command by typing ``history -h`` at any ``cmd2`` input prompt, or by exploring :ref:`Command History For Users `. - - -Conclusion ----------- - -You've just created a simple, but functional command line application. With minimal work -on your part, the application leverages many robust features of ``cmd2``. To learn more -you can: - -- Dive into all of the :doc:`../features/index` that ``cmd2`` provides -- Look at more :doc:`../examples/index` -- Browse the :doc:`../api/index` diff --git a/docs/examples/first_app.rst b/docs/examples/first_app.rst new file mode 100644 index 00000000..0b1984c5 --- /dev/null +++ b/docs/examples/first_app.rst @@ -0,0 +1,303 @@ +First Application +================= + +.. _cmd: https://docs.python.org/3/library/cmd.html + +Here's a quick walkthrough of a simple application which demonstrates 8 features of +``cmd2``: + +* Settings +* Commands +* Argument Parsing +* Generating Output +* Help +* Shortcuts +* Multiline Commands +* History + +If you don't want to type as we go, you can download the complete source for +this example. + + +Basic Application +----------------- + +First we need to create a new ``cmd2`` application. Create a new file ``first_app.py`` with the following contents:: + + #!/usr/bin/env python + """A simple cmd2 application.""" + import cmd2 + + + class FirstApp(cmd2.Cmd): + """A simple cmd2 application.""" + + + if __name__ == '__main__': + import sys + c = FirstApp() + sys.exit(c.cmdloop()) + +We have a new class ``FirstApp`` which is a subclass of +:ref:`api/cmd:cmd2.Cmd`. When we tell python to run our file like this: + +.. code-block:: shell + + $ python first_app.py + +it creates an instance of our class, and calls the ``cmdloop()`` method. This +method accepts user input and runs commands based on that input. Because we +subclassed ``cmd2.Cmd``, our new app already has a bunch of features built in. + +Congratulations, you have a working ``cmd2`` app. You can run it, and then type +``quit`` to exit. + + +Create a New Setting +-------------------- + +Before we create our first command, we are going to add a setting to this app. +``cmd2`` includes robust support for :ref:`features/settings:Settings`. You +configure settings during object initialization, so we need to add an +initializer to our class:: + + def __init__(self): + super().__init__() + + # Make maxrepeats settable at runtime + self.maxrepeats = 3 + self.settable['maxrepeats'] = 'max repetitions for speak command' + +In that initializer, the first thing to do is to make sure we initialize +``cmd2``. That's what the ``super().__init__()`` line does. Then we create an +attribute to hold our setting, and then add a description of our setting to the +``settable`` dictionary. If our attribute name isn't in ``settable``, then it +won't be treated as a setting. Now if you run the script, and enter the ``set`` +command to see the settings, like this: + +.. code-block:: shell + + $ python first_app.py + (Cmd) set + +you will see our ``maxrepeats`` setting show up with it's default value of ``3``. + + +Create A Command +---------------- + +Now we will create our first command, called ``speak`` which will echo back +whatever we tell it to say. We are going to use an :ref:`argument processor +` so the ``speak`` command can +shout and talk piglatin. We will also use some built in methods for +:ref:`generating output `. Add +this code to ``first_app.py``, so that the ``speak_parser`` attribute and the +``do_speak()`` method are part of the ``CmdLineApp()`` class:: + + speak_parser = argparse.ArgumentParser() + speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') + speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') + speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') + speak_parser.add_argument('words', nargs='+', help='words to say') + + @cmd2.with_argparser(speak_parser) + def do_speak(self, args): + """Repeats what you tell me to.""" + words = [] + for word in args.words: + if args.piglatin: + word = '%s%say' % (word[1:], word[0]) + if args.shout: + word = word.upper() + words.append(word) + repetitions = args.repeat or 1 + for _ in range(min(repetitions, self.maxrepeats)): + # .poutput handles newlines, and accommodates output redirection too + self.poutput(' '.join(words)) + +Up at the top of the script, you'll also need to add:: + + import argparse + +There's a bit to unpack here, so let's walk through it. We created +``speak_parser``, which uses the `argparse +`_ module from the Python +standard library to parse command line input from a user. There is nothing thus +far that is specific to ``cmd2``. + +There is also a new method called ``do_speak()``. In both cmd_ and ``cmd2``, +methods that start with ``do_`` become new commands, so by defining this method +we have created a command called ``speak``. + +Note the ``@cmd2.with_argparser`` decorator on the ``do_speak()`` method. This +decorator does 3 useful things for us: + +1. It tells ``cmd2`` to process all input for the ``speak`` command using the + argparser we defined. If the user input doesn't meet the requirements defined by + the argparser, then an error will be displayed for the user. +2. It alters our ``do_speak`` method so that instead of receiving the raw user + input as a parameter, we receive the namespace from the argparser. +3. It creates a help message for us based on the argparser. + +You can see in the body of the method how we use the namespace from the +argparser (passed in as the variable ``args``). We build an array of words which +we will output, honoring both the ``--piglatin`` and ``--shout`` options. + +At the end of the method, we use our ``maxrepeats`` setting as an upper limit to +the number of times we will print the output. + +The last thing you'll notice is that we used the ``self.poutput()`` method to +display our output. ``poutput()`` is a method provided by ``cmd2``, which I +strongly recommend you use anytime you want to :ref:`generate output `. It provides the following benefits: + +1. Allows the user to redirect output to a text file or pipe it to a shell process +2. Gracefully handles ``BrokenPipeWarning`` exceptions for redirected output +3. Makes the output show up in a :ref:`transcript ` +4. Honors the setting to strip embedded ansi sequences (typically used for background and foreground colors) + +Go run the script again, and try out the ``speak`` command. Try typing ``help speak``, and you +will see a lovely usage message describing the various options for the command. + +With those few lines of code, we created a :ref:`command +`, used an :ref:`Argument Processor `, added a nice :ref:`help message ` for our users, and :ref:`generated some output `. + + +Shortcuts +--------- + +``cmd2`` has several capabilities to simplify repetitive user input: :ref:`Shortcuts, Aliases, and Macros `. Let's add a shortcut to our application. Shortcuts are character strings that can be used instead of a command name. For example, ``cmd2`` has support for a shortcut ``!`` which runs the ``shell`` command. So instead of typing this: + +.. code-block:: shell + + (Cmd) shell ls -al + +you can type this: + +.. code-block:: shell + + (Cmd) !ls -al + +Let's add a shortcut for our ``speak`` command. Change the ``__init__()`` method so it looks like this:: + + def __init__(self): + shortcuts = cmd2.DEFAULT_SHORTCUTS + shortcuts.update({'&': 'speak'}) + super().__init__(shortcuts=shortcuts) + + # Make maxrepeats settable at runtime + self.maxrepeats = 3 + self.settable['maxrepeats'] = 'max repetitions for speak command' + +Shortcuts are passed to the ``cmd2`` initializer, and if you want the built-in +shortcuts of ``cmd2`` you have to pass them. These shortcuts are defined as a +dictionary, with the key being the shortcut, and the value containing the +command. When using the default shortcuts and also adding your own, it's a good +idea to use the ``.update()`` method to modify the dictionary. This way if you +add a shortcut that happens to already be in the default set, yours will +override, and you won't get any errors at runtime. + +Run your app again, and type: + +.. code-block:: shell + + (Cmd) shortcuts + +to see the list of all of the shortcuts, including the one for speak that we just created. + + +Multiline Commands +------------------ + +Some use cases benefit from the ability to have commands that span more than one +line. For example, you might want the ability for your user to type in a SQL +command, which can often span lines and which are terminated with a semicolon. +Let's add a :ref:`multiline command ` to our application. First we'll create a new command called +``orate``. This code shows both the definition of our ``speak`` command, and the +``orate`` command:: + + @cmd2.with_argparser(speak_parser) + def do_speak(self, args): + """Repeats what you tell me to.""" + words = [] + for word in args.words: + if args.piglatin: + word = '%s%say' % (word[1:], word[0]) + if args.shout: + word = word.upper() + words.append(word) + repetitions = args.repeat or 1 + for _ in range(min(repetitions, self.maxrepeats)): + # .poutput handles newlines, and accommodates output redirection too + self.poutput(' '.join(words)) + + # orate is a synonym for speak which takes multiline input + do_orate = do_speak + +With the new command created, we need to tell ``cmd2`` to treat that command as +a multi-line command. Modify the super initialization line to look like this:: + + super().__init__(multiline_commands=['orate'], shortcuts=shortcuts) + +Now when you run the example, you can type something like this: + +.. code-block:: shell + + (Cmd) orate O for a Muse of fire, that would ascend + > The brightest heaven of invention, + > A kingdom for a stage, princes to act + > And monarchs to behold the swelling scene! ; + +Notice the prompt changes to indicate that input is still ongoing. ``cmd2`` will +continue prompting for input until it sees an unquoted semicolon (the default +multi-line command termination character). + + +History +------- + +``cmd2`` tracks the history of the commands that users enter. As a developer, +you don't need to do anything to enable this functionality, you get it for free. +If you want the history of commands to persist between invocations of your +application, you'll need to do a little work. The :ref:`features/history:Command +History` page has all the details. + +Users can access command history using two methods: + +- the `readline `_ library + which provides a python interface to the `GNU readline library + `_ +- the ``history`` command which is built-in to ``cmd2`` + +From the prompt in a ``cmd2``-based application, you can press ``Control-p`` to +move to the previously entered command, and ``Control-n`` to move to the next +command. You can also search through the command history using ``Control-r``. +The `GNU Readline User Manual +`_ has all the +details, including all the available commands, and instructions for customizing +the key bindings. + +The ``history`` command allows a user to view the command history, and select +commands from history by number, range, string search, or regular expression. +With the selected commands, users can: + +- re-run the commands +- edit the selected commands in a text editor, and run them after the text editor exits +- save the commands to a file +- run the commands, saving both the commands and their output to a file + +Learn more about the ``history`` command by typing ``history -h`` at any +``cmd2`` input prompt, or by exploring :ref:`Command History For Users +`. + + +Conclusion +---------- + +You've just created a simple, but functional command line application. With +minimal work on your part, the application leverages many robust features of +``cmd2``. To learn more you can: + +- Dive into all of the :doc:`../features/index` that ``cmd2`` provides +- Look at more :doc:`../examples/index` +- Browse the :doc:`../api/index` diff --git a/docs/examples/index.rst b/docs/examples/index.rst index c5fbae3e..2070b7a3 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -4,4 +4,4 @@ Examples .. toctree:: :maxdepth: 1 - example + first_app diff --git a/docs/overview/featuretour.rst b/docs/overview/featuretour.rst deleted file mode 100644 index a0d33da4..00000000 --- a/docs/overview/featuretour.rst +++ /dev/null @@ -1,5 +0,0 @@ -Feature Highlights -================== - -[Briefly describe the list of major features, linking to the more detailed description -of each features elsewhere in the documentation.] diff --git a/docs/overview/index.rst b/docs/overview/index.rst index 831e9bf0..231191c1 100644 --- a/docs/overview/index.rst +++ b/docs/overview/index.rst @@ -5,9 +5,8 @@ Getting Started :maxdepth: 1 :hidden: - featuretour - ../examples/example installation + ../examples/first_app integrating alternatives resources diff --git a/docs/overview/summary.rst b/docs/overview/summary.rst index 17d4732d..b59c86c2 100644 --- a/docs/overview/summary.rst +++ b/docs/overview/summary.rst @@ -11,11 +11,9 @@ want to add more functionality with very little work? ``cmd2`` is a powerful python library for building command line applications. Start here to find out if this library is a good fit for your needs. -* :ref:`overview/featuretour:Feature Highlights` - a quick tour of a few of the - features in ``cmd2``, for both developers and users -* :ref:`examples/example:Simple Application` - a sample application showing 5 key features of ``cmd2`` * :ref:`overview/installation:Installation Instructions` - how to install ``cmd2`` and associated optional dependencies +* :ref:`examples/first_app:First Application` - a sample application showing 8 key features of ``cmd2`` * :ref:`overview/integrating:Integrate cmd2 Into Your Project` - adding ``cmd2`` to your project * :ref:`overview/alternatives:Alternatives` - other python packages that might diff --git a/examples/first_app.py b/examples/first_app.py new file mode 100755 index 00000000..4c2d977c --- /dev/null +++ b/examples/first_app.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +A simple application using cmd2 which demonstrates 8 key features: + + * Settings + * Commands + * Argument Parsing + * Generating Output + * Help + * Shortcuts + * Multiline Commands + * History +""" +import argparse +import random + +import cmd2 + + +class FirstApp(cmd2.Cmd): + """A simple cmd2 application.""" + + def __init__(self): + shortcuts = cmd2.DEFAULT_SHORTCUTS + shortcuts.update({'&': 'speak'}) + super().__init__(multiline_commands=['orate'], shortcuts=shortcuts) + + # Make maxrepeats settable at runtime + self.maxrepeats = 3 + self.settable['maxrepeats'] = 'max repetitions for speak command' + + speak_parser = argparse.ArgumentParser() + speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') + speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') + speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') + speak_parser.add_argument('words', nargs='+', help='words to say') + + @cmd2.with_argparser(speak_parser) + def do_speak(self, args): + """Repeats what you tell me to.""" + words = [] + for word in args.words: + if args.piglatin: + word = '%s%say' % (word[1:], word[0]) + if args.shout: + word = word.upper() + words.append(word) + repetitions = args.repeat or 1 + for _ in range(min(repetitions, self.maxrepeats)): + # .poutput handles newlines, and accommodates output redirection too + self.poutput(' '.join(words)) + + # orate is a synonym for speak which takes multiline input + do_orate = do_speak + + +if __name__ == '__main__': + import sys + c = FirstApp() + sys.exit(c.cmdloop()) -- cgit v1.2.1