| @@ -7,7 +7,6 @@ earwigbot Package | |||||
| .. automodule:: earwigbot.__init__ | .. automodule:: earwigbot.__init__ | ||||
| :members: | :members: | ||||
| :undoc-members: | :undoc-members: | ||||
| :show-inheritance: | |||||
| :mod:`bot` Module | :mod:`bot` Module | ||||
| ----------------- | ----------------- | ||||
| @@ -15,7 +14,6 @@ earwigbot Package | |||||
| .. automodule:: earwigbot.bot | .. automodule:: earwigbot.bot | ||||
| :members: | :members: | ||||
| :undoc-members: | :undoc-members: | ||||
| :show-inheritance: | |||||
| :mod:`config` Module | :mod:`config` Module | ||||
| -------------------- | -------------------- | ||||
| @@ -23,7 +21,6 @@ earwigbot Package | |||||
| .. automodule:: earwigbot.config | .. automodule:: earwigbot.config | ||||
| :members: | :members: | ||||
| :undoc-members: | :undoc-members: | ||||
| :show-inheritance: | |||||
| :mod:`exceptions` Module | :mod:`exceptions` Module | ||||
| ------------------------ | ------------------------ | ||||
| @@ -37,7 +34,7 @@ earwigbot Package | |||||
| ---------------------- | ---------------------- | ||||
| .. automodule:: earwigbot.managers | .. automodule:: earwigbot.managers | ||||
| :members: | |||||
| :members: _ResourceManager, CommandManager, TaskManager | |||||
| :undoc-members: | :undoc-members: | ||||
| :show-inheritance: | :show-inheritance: | ||||
| @@ -47,7 +44,6 @@ earwigbot Package | |||||
| .. automodule:: earwigbot.util | .. automodule:: earwigbot.util | ||||
| :members: | :members: | ||||
| :undoc-members: | :undoc-members: | ||||
| :show-inheritance: | |||||
| Subpackages | Subpackages | ||||
| ----------- | ----------- | ||||
| @@ -57,7 +57,7 @@ The most useful attributes are: | |||||
| logged and used as the quit message when disconnecting from IRC. | logged and used as the quit message when disconnecting from IRC. | ||||
| :py:class:`earwigbot.config.BotConfig` stores configuration information for the | :py:class:`earwigbot.config.BotConfig` stores configuration information for the | ||||
| bot. Its docstring explains what each attribute is used for, but essentially | |||||
| bot. Its docstrings explains what each attribute is used for, but essentially | |||||
| each "node" (one of :py:attr:`config.components`, :py:attr:`wiki`, | each "node" (one of :py:attr:`config.components`, :py:attr:`wiki`, | ||||
| :py:attr:`tasks`, :py:attr:`tasks`, or :py:attr:`metadata`) maps to a section | :py:attr:`tasks`, :py:attr:`tasks`, or :py:attr:`metadata`) maps to a section | ||||
| of the bot's :file:`config.yml` file. For example, if :file:`config.yml` | of the bot's :file:`config.yml` file. For example, if :file:`config.yml` | ||||
| @@ -21,11 +21,12 @@ | |||||
| # SOFTWARE. | # SOFTWARE. | ||||
| """ | """ | ||||
| EarwigBot is a Python robot that edits Wikipedia and interacts with people over | |||||
| IRC. - https://github.com/earwig/earwigbot | |||||
| `EarwigBot <https://github.com/earwig/earwigbot>`_ is a Python robot that edits | |||||
| Wikipedia and interacts with people over IRC. | |||||
| See README.rst for an overview, or the docs/ directory for details. This | |||||
| documentation is also available online at http://packages.python.org/earwigbot. | |||||
| See :file:`README.rst` for an overview, or the :file:`docs/` directory for | |||||
| details. This documentation is also available `online | |||||
| <http://packages.python.org/earwigbot>`_. | |||||
| """ | """ | ||||
| __author__ = "Ben Kurtovic" | __author__ = "Ben Kurtovic" | ||||
| @@ -40,18 +40,21 @@ class Bot(object): | |||||
| EarwigBot has three components that can run independently of each other: an | EarwigBot has three components that can run independently of each other: an | ||||
| IRC front-end, an IRC watcher, and a wiki scheduler. | IRC front-end, an IRC watcher, and a wiki scheduler. | ||||
| * The IRC front-end runs on a normal IRC server and expects users to | |||||
| - The IRC front-end runs on a normal IRC server and expects users to | |||||
| interact with it/give it commands. | interact with it/give it commands. | ||||
| * The IRC watcher runs on a wiki recent-changes server and listens for | |||||
| - The IRC watcher runs on a wiki recent-changes server and listens for | |||||
| edits. Users cannot interact with this part of the bot. | edits. Users cannot interact with this part of the bot. | ||||
| * The wiki scheduler runs wiki-editing bot tasks in separate threads at | |||||
| - The wiki scheduler runs wiki-editing bot tasks in separate threads at | |||||
| user-defined times through a cron-like interface. | user-defined times through a cron-like interface. | ||||
| The Bot() object is accessable from within commands and tasks as self.bot. | |||||
| This is the primary way to access data from other components of the bot. | |||||
| For example, our BotConfig object is accessable from bot.config, tasks | |||||
| can be started with bot.tasks.start(), and sites can be loaded from the | |||||
| wiki toolset with bot.wiki.get_site(). | |||||
| The :py:class:`Bot` object is accessable from within commands and tasks as | |||||
| :py:attr:`self.bot`. This is the primary way to access data from other | |||||
| components of the bot. For example, our | |||||
| :py:class:`~earwigbot.config.BotConfig` object is accessable from | |||||
| :py:attr:`bot.config`, tasks can be started with | |||||
| :py:meth:`bot.tasks.start <earwigbot.managers.TaskManager.start>`, and | |||||
| sites can be loaded from the wiki toolset with :py:meth:`bot.wiki.get_site | |||||
| <earwigbot.wiki.sitesdb.SitesDB.get_site>`. | |||||
| """ | """ | ||||
| def __init__(self, root_dir, level=logging.INFO): | def __init__(self, root_dir, level=logging.INFO): | ||||
| @@ -160,11 +163,11 @@ class Bot(object): | |||||
| This is thread-safe, and it will gracefully stop IRC components before | This is thread-safe, and it will gracefully stop IRC components before | ||||
| reloading anything. Note that you can safely reload commands or tasks | reloading anything. Note that you can safely reload commands or tasks | ||||
| without restarting the bot with bot.commands.load() or | |||||
| bot.tasks.load(). These should not interfere with running components | |||||
| or tasks. | |||||
| without restarting the bot with :py:meth:`bot.commands.load` or | |||||
| :py:meth:`bot.tasks.load`. These should not interfere with running | |||||
| components or tasks. | |||||
| If given, 'msg' will be used as our quit message. | |||||
| If given, *msg* will be used as our quit message. | |||||
| """ | """ | ||||
| if msg: | if msg: | ||||
| self.logger.info('Restarting bot ("{0}")'.format(msg)) | self.logger.info('Restarting bot ("{0}")'.format(msg)) | ||||
| @@ -180,7 +183,7 @@ class Bot(object): | |||||
| def stop(self, msg=None): | def stop(self, msg=None): | ||||
| """Gracefully stop all bot components. | """Gracefully stop all bot components. | ||||
| If given, 'msg' will be used as our quit message. | |||||
| If given, *msg* will be used as our quit message. | |||||
| """ | """ | ||||
| if msg: | if msg: | ||||
| self.logger.info('Stopping bot ("{0}")'.format(msg)) | self.logger.info('Stopping bot ("{0}")'.format(msg)) | ||||
| @@ -29,36 +29,34 @@ from os import mkdir, path | |||||
| from Crypto.Cipher import Blowfish | from Crypto.Cipher import Blowfish | ||||
| import yaml | import yaml | ||||
| from earwigbot.exceptions import NoConfigError | |||||
| __all__ = ["BotConfig"] | __all__ = ["BotConfig"] | ||||
| class BotConfig(object): | class BotConfig(object): | ||||
| """ | """ | ||||
| EarwigBot's YAML Config File Manager | |||||
| **EarwigBot's YAML Config File Manager** | |||||
| This handles all tasks involving reading and writing to our config file, | This handles all tasks involving reading and writing to our config file, | ||||
| including encrypting and decrypting passwords and making a new config file | including encrypting and decrypting passwords and making a new config file | ||||
| from scratch at the inital bot run. | from scratch at the inital bot run. | ||||
| BotConfig has a few properties and functions, including the following: | |||||
| * config.root_dir - bot's working directory; contains config.yml, logs/ | |||||
| * config.path - path to the bot's config file | |||||
| * config.components - enabled components | |||||
| * config.wiki - information about wiki-editing | |||||
| * config.tasks - information for bot tasks | |||||
| * config.irc - information about IRC | |||||
| * config.metadata - miscellaneous information | |||||
| * config.schedule() - tasks scheduled to run at a given time | |||||
| BotConfig also has some functions used in config loading: | |||||
| * config.load() - loads and parses our config file, returning True if | |||||
| passwords are stored encrypted or False otherwise; | |||||
| can also be used to easily reload config | |||||
| * config.decrypt() - given a key, decrypts passwords inside our config | |||||
| variables, and remembers to decrypt the password if | |||||
| config is reloaded; won't do anything if passwords | |||||
| aren't encrypted | |||||
| BotConfig has a few attributes and methods, including the following: | |||||
| - :py:attr:`root_dir`: bot's working directory; contains | |||||
| :file:`config.yml`, :file:`logs/` | |||||
| - :py:attr:`path`: path to the bot's config file | |||||
| - :py:attr:`components`: enabled components | |||||
| - :py:attr:`wiki`: information about wiki-editing | |||||
| - :py:attr:`tasks`: information for bot tasks | |||||
| - :py:attr:`irc`: information about IRC | |||||
| - :py:attr:`metadata`: miscellaneous information | |||||
| - :py:meth:`schedule`: tasks scheduled to run at a given time | |||||
| BotConfig also has some methods used in config loading: | |||||
| - :py:meth:`load`: loads (or reloads) and parses our config file | |||||
| - :py:meth:`decrypt`: decrypts an object in the config tree | |||||
| """ | """ | ||||
| def __init__(self, root_dir, level): | def __init__(self, root_dir, level): | ||||
| @@ -159,10 +157,12 @@ class BotConfig(object): | |||||
| @property | @property | ||||
| def root_dir(self): | def root_dir(self): | ||||
| """The bot's root directory containing its config file and more.""" | |||||
| return self._root_dir | return self._root_dir | ||||
| @property | @property | ||||
| def logging_level(self): | def logging_level(self): | ||||
| """The minimum logging level for messages logged via stdout.""" | |||||
| return self._logging_level | return self._logging_level | ||||
| @logging_level.setter | @logging_level.setter | ||||
| @@ -172,15 +172,17 @@ class BotConfig(object): | |||||
| @property | @property | ||||
| def path(self): | def path(self): | ||||
| """The path to the bot's config file.""" | |||||
| return self._config_path | return self._config_path | ||||
| @property | @property | ||||
| def log_dir(self): | def log_dir(self): | ||||
| """The directory containing the bot's logs.""" | |||||
| return self._log_dir | return self._log_dir | ||||
| @property | @property | ||||
| def data(self): | def data(self): | ||||
| """The entire config file.""" | |||||
| """The entire config file as a decoded JSON object.""" | |||||
| return self._data | return self._data | ||||
| @property | @property | ||||
| @@ -209,11 +211,11 @@ class BotConfig(object): | |||||
| return self._metadata | return self._metadata | ||||
| def is_loaded(self): | def is_loaded(self): | ||||
| """Return True if our config file has been loaded, otherwise False.""" | |||||
| """Return ``True`` if our config file has been loaded, or ``False``.""" | |||||
| return self._data is not None | return self._data is not None | ||||
| def is_encrypted(self): | def is_encrypted(self): | ||||
| """Return True if passwords are encrypted, otherwise False.""" | |||||
| """Return ``True`` if passwords are encrypted, otherwise ``False``.""" | |||||
| return self.metadata.get("encryptPasswords", False) | return self.metadata.get("encryptPasswords", False) | ||||
| def load(self): | def load(self): | ||||
| @@ -223,12 +225,14 @@ class BotConfig(object): | |||||
| user. If there is no config file at all, offer to make one, otherwise | user. If there is no config file at all, offer to make one, otherwise | ||||
| exit. | exit. | ||||
| Store data from our config file in five _ConfigNodes (components, | |||||
| wiki, tasks, irc, metadata) for easy access (as well as the internal | |||||
| _data variable). | |||||
| If config is being reloaded, encrypted items will be automatically | |||||
| decrypted if they were decrypted beforehand. | |||||
| Data from the config file is stored in five | |||||
| :py:class:`~earwigbot.config._ConfigNode` s (:py:attr:`components`, | |||||
| :py:attr:`wiki`, :py:attr:`tasks`, :py:attr:`irc`, :py:attr:`metadata`) | |||||
| for easy access (as well as the lower-level :py:attr:`data` attribute). | |||||
| If passwords are encrypted, we'll use :py:func:`~getpass.getpass` for | |||||
| the key and then decrypt them. If the config is being reloaded, | |||||
| encrypted items will be automatically decrypted if they were decrypted | |||||
| earlier. | |||||
| """ | """ | ||||
| if not path.exists(self._config_path): | if not path.exists(self._config_path): | ||||
| print "Config file not found:", self._config_path | print "Config file not found:", self._config_path | ||||
| @@ -236,7 +240,7 @@ class BotConfig(object): | |||||
| if choice.lower().startswith("y"): | if choice.lower().startswith("y"): | ||||
| self._make_new() | self._make_new() | ||||
| else: | else: | ||||
| exit(1) # TODO: raise an exception instead | |||||
| raise NoConfigError() | |||||
| self._load() | self._load() | ||||
| data = self._data | data = self._data | ||||
| @@ -255,16 +259,19 @@ class BotConfig(object): | |||||
| self._decrypt(node, nodes) | self._decrypt(node, nodes) | ||||
| def decrypt(self, node, *nodes): | def decrypt(self, node, *nodes): | ||||
| """Use self._decryption_cipher to decrypt an object in our config tree. | |||||
| """Decrypt an object in our config tree. | |||||
| :py:attr:`_decryption_cipher` is used as our key, retrieved using | |||||
| :py:func:`~getpass.getpass` in :py:meth:`load` if it wasn't already | |||||
| specified. If this is called when passwords are not encrypted (check | |||||
| with :py:meth:`is_encrypted`), nothing will happen. We'll also keep | |||||
| track of this node if :py:meth:`load` is called again (i.e. to reload) | |||||
| and automatically decrypt it. | |||||
| If this is called when passwords are not encrypted (check with | |||||
| config.is_encrypted()), nothing will happen. We'll also keep track of | |||||
| this node if config.load() is called again (i.e. to reload) and | |||||
| automatically decrypt it. | |||||
| Example usage:: | |||||
| Example usage: | |||||
| config.decrypt(config.irc, "frontend", "nickservPassword") | |||||
| -> decrypts config.irc["frontend"]["nickservPassword"] | |||||
| >>> config.decrypt(config.irc, "frontend", "nickservPassword") | |||||
| # decrypts config.irc["frontend"]["nickservPassword"] | |||||
| """ | """ | ||||
| self._decryptable_nodes.append((node, nodes)) | self._decryptable_nodes.append((node, nodes)) | ||||
| if self.is_encrypted(): | if self.is_encrypted(): | ||||
| @@ -273,9 +280,8 @@ class BotConfig(object): | |||||
| def schedule(self, minute, hour, month_day, month, week_day): | def schedule(self, minute, hour, month_day, month, week_day): | ||||
| """Return a list of tasks scheduled to run at the specified time. | """Return a list of tasks scheduled to run at the specified time. | ||||
| The schedule data comes from our config file's 'schedule' field, which | |||||
| is stored as self._data["schedule"]. Call this function as | |||||
| config.schedule(args). | |||||
| The schedule data comes from our config file's ``schedule`` field, | |||||
| which is stored as :py:attr:`self.data["schedule"] <data>`. | |||||
| """ | """ | ||||
| # Tasks to run this turn, each as a list of either [task_name, kwargs], | # Tasks to run this turn, each as a list of either [task_name, kwargs], | ||||
| # or just the task_name: | # or just the task_name: | ||||
| @@ -26,6 +26,7 @@ EarwigBot Exceptions | |||||
| This module contains all exceptions used by EarwigBot:: | This module contains all exceptions used by EarwigBot:: | ||||
| EarwigBotError | EarwigBotError | ||||
| +-- NoConfigError | |||||
| +-- IRCError | +-- IRCError | ||||
| | +-- BrokenSocketError | | +-- BrokenSocketError | ||||
| | +-- KwargParseError | | +-- KwargParseError | ||||
| @@ -55,6 +56,13 @@ This module contains all exceptions used by EarwigBot:: | |||||
| class EarwigBotError(Exception): | class EarwigBotError(Exception): | ||||
| """Base exception class for errors in EarwigBot.""" | """Base exception class for errors in EarwigBot.""" | ||||
| class NoConfigError(EarwigBotError): | |||||
| """The bot cannot be run without a config file. | |||||
| This occurs if no config file exists, and the user said they did not want | |||||
| one to be created. | |||||
| """ | |||||
| class IRCError(EarwigBotError): | class IRCError(EarwigBotError): | ||||
| """Base exception class for errors in IRC-relation sections of the bot.""" | """Base exception class for errors in IRC-relation sections of the bot.""" | ||||
| @@ -34,21 +34,21 @@ __all__ = ["CommandManager", "TaskManager"] | |||||
| class _ResourceManager(object): | class _ResourceManager(object): | ||||
| """ | """ | ||||
| EarwigBot's Base Resource Manager | |||||
| Resources are essentially objects dynamically loaded by the bot, both | Resources are essentially objects dynamically loaded by the bot, both | ||||
| packaged with it (built-in resources) and created by users (plugins, aka | packaged with it (built-in resources) and created by users (plugins, aka | ||||
| custom resources). Currently, the only two types of resources are IRC | custom resources). Currently, the only two types of resources are IRC | ||||
| commands and bot tasks. These are both loaded from two locations: the | commands and bot tasks. These are both loaded from two locations: the | ||||
| earwigbot.commands and earwigbot.tasks packages, and the commands/ and | |||||
| tasks/ directories within the bot's working directory. | |||||
| This class handles the low-level tasks of (re)loading resources via load(), | |||||
| retrieving specific resources via get(), and iterating over all resources | |||||
| via __iter__(). If iterating over resources, it is recommended to acquire | |||||
| self.lock beforehand and release it afterwards (alternatively, wrap your | |||||
| code in a `with` statement) so an attempt at reloading resources in another | |||||
| thread won't disrupt your iteration. | |||||
| :py:mod:`earwigbot.commands` and :py:mod:`earwigbot.tasks packages`, and | |||||
| the :file:`commands/` and :file:`tasks/` directories within the bot's | |||||
| working directory. | |||||
| This class handles the low-level tasks of (re)loading resources via | |||||
| :py:meth:`load`, retrieving specific resources via :py:meth:`get`, and | |||||
| iterating over all resources via :py:meth:`__iter__`. If iterating over | |||||
| resources, it is recommended to acquire :py:attr:`self.lock <lock>` | |||||
| beforehand and release it afterwards (alternatively, wrap your code in a | |||||
| ``with`` statement) so an attempt at reloading resources in another thread | |||||
| won't disrupt your iteration. | |||||
| """ | """ | ||||
| def __init__(self, bot, name, attribute, base): | def __init__(self, bot, name, attribute, base): | ||||
| self.bot = bot | self.bot = bot | ||||
| @@ -62,6 +62,7 @@ class _ResourceManager(object): | |||||
| @property | @property | ||||
| def lock(self): | def lock(self): | ||||
| """The resource access/modify lock.""" | |||||
| return self._resource_access_lock | return self._resource_access_lock | ||||
| def __iter__(self): | def __iter__(self): | ||||
| @@ -116,7 +117,7 @@ class _ResourceManager(object): | |||||
| processed.append(modname) | processed.append(modname) | ||||
| def load(self): | def load(self): | ||||
| """Load (or reload) all valid resources into self._resources.""" | |||||
| """Load (or reload) all valid resources into :py:attr:`_resources`.""" | |||||
| name = self._resource_name # e.g. "commands" or "tasks" | name = self._resource_name # e.g. "commands" or "tasks" | ||||
| with self.lock: | with self.lock: | ||||
| self._resources.clear() | self._resources.clear() | ||||
| @@ -132,15 +133,14 @@ class _ResourceManager(object): | |||||
| def get(self, key): | def get(self, key): | ||||
| """Return the class instance associated with a certain resource. | """Return the class instance associated with a certain resource. | ||||
| Will raise KeyError if the resource (command or task) is not found. | |||||
| Will raise :py:exc:`KeyError` if the resource (a command or task) is | |||||
| not found. | |||||
| """ | """ | ||||
| return self._resources[key] | return self._resources[key] | ||||
| class CommandManager(_ResourceManager): | class CommandManager(_ResourceManager): | ||||
| """ | """ | ||||
| EarwigBot's IRC Command Manager | |||||
| Manages (i.e., loads, reloads, and calls) IRC commands. | Manages (i.e., loads, reloads, and calls) IRC commands. | ||||
| """ | """ | ||||
| def __init__(self, bot): | def __init__(self, bot): | ||||
| @@ -164,7 +164,7 @@ class CommandManager(_ResourceManager): | |||||
| self.logger.exception(e.format(command.name)) | self.logger.exception(e.format(command.name)) | ||||
| def call(self, hook, data): | def call(self, hook, data): | ||||
| """Given a hook type and a Data object, respond appropriately.""" | |||||
| """Respond to a hook type and a :py:class:`Data` object.""" | |||||
| self.lock.acquire() | self.lock.acquire() | ||||
| for command in self._resources.itervalues(): | for command in self._resources.itervalues(): | ||||
| if hook in command.hooks and self._wrap_check(command, data): | if hook in command.hooks and self._wrap_check(command, data): | ||||
| @@ -176,8 +176,6 @@ class CommandManager(_ResourceManager): | |||||
| class TaskManager(_ResourceManager): | class TaskManager(_ResourceManager): | ||||
| """ | """ | ||||
| EarwigBot's Bot Task Manager | |||||
| Manages (i.e., loads, reloads, schedules, and runs) wiki bot tasks. | Manages (i.e., loads, reloads, schedules, and runs) wiki bot tasks. | ||||
| """ | """ | ||||
| def __init__(self, bot): | def __init__(self, bot): | ||||
| @@ -197,8 +195,9 @@ class TaskManager(_ResourceManager): | |||||
| def start(self, task_name, **kwargs): | def start(self, task_name, **kwargs): | ||||
| """Start a given task in a new daemon thread, and return the thread. | """Start a given task in a new daemon thread, and return the thread. | ||||
| kwargs are passed to task.run(). If the task is not found, None will be | |||||
| returned. | |||||
| kwargs are passed to :py:meth:`task.run() <earwigbot.tasks.BaseTask>`. | |||||
| If the task is not found, ``None`` will be returned an an error is | |||||
| logged. | |||||
| """ | """ | ||||
| msg = "Starting task '{0}' in a new thread" | msg = "Starting task '{0}' in a new thread" | ||||
| self.logger.info(msg.format(task_name)) | self.logger.info(msg.format(task_name)) | ||||
| @@ -22,8 +22,27 @@ | |||||
| # SOFTWARE. | # SOFTWARE. | ||||
| """ | """ | ||||
| This is EarwigBot's command-line utility, enabling you to easily start the | |||||
| bot or run specific tasks. | |||||
| usage: :command:`earwigbot [-h] [-v] [-d] [-q] [-t NAME] [PATH]` | |||||
| This is EarwigBot's command-line utility, enabling you to easily start the bot | |||||
| or run specific tasks. | |||||
| .. glossary:: | |||||
| ``PATH`` | |||||
| path to the bot's working directory, which will be created if it doesn't | |||||
| exist; current directory assumed if not specified | |||||
| ``-h``, ``--help`` | |||||
| show this help message and exit | |||||
| ``-v``, ``--version`` | |||||
| show program's version number and exit | |||||
| ``-d``, ``--debug`` | |||||
| print all logs, including ``DEBUG``-level messages | |||||
| ``-q``, ``--quiet`` | |||||
| don't print any logs except warnings and errors | |||||
| ``-t NAME``, ``--task NAME`` | |||||
| given the name of a task, the bot will run it instead of the main bot and | |||||
| then exit | |||||
| """ | """ | ||||
| from argparse import ArgumentParser | from argparse import ArgumentParser | ||||
| @@ -37,17 +56,23 @@ from earwigbot.bot import Bot | |||||
| __all__ = ["main"] | __all__ = ["main"] | ||||
| def main(): | def main(): | ||||
| """Main entry point for the command-line utility.""" | |||||
| version = "EarwigBot v{0}".format(__version__) | version = "EarwigBot v{0}".format(__version__) | ||||
| parser = ArgumentParser(description=__doc__) | |||||
| desc = """This is EarwigBot's command-line utility, enabling you to easily | |||||
| start the bot or run specific tasks.""" | |||||
| parser = ArgumentParser(description=desc) | |||||
| parser.add_argument("path", nargs="?", metavar="PATH", default=path.curdir, | parser.add_argument("path", nargs="?", metavar="PATH", default=path.curdir, | ||||
| help="path to the bot's working directory, which will be created if it doesn't exist; current directory assumed if not specified") | |||||
| help="""path to the bot's working directory, which will | |||||
| be created if it doesn't exist; current | |||||
| directory assumed if not specified""") | |||||
| parser.add_argument("-v", "--version", action="version", version=version) | parser.add_argument("-v", "--version", action="version", version=version) | ||||
| parser.add_argument("-d", "--debug", action="store_true", | parser.add_argument("-d", "--debug", action="store_true", | ||||
| help="print all logs, including DEBUG-level messages") | help="print all logs, including DEBUG-level messages") | ||||
| parser.add_argument("-q", "--quiet", action="store_true", | parser.add_argument("-q", "--quiet", action="store_true", | ||||
| help="don't print any logs except warnings and errors") | help="don't print any logs except warnings and errors") | ||||
| parser.add_argument("-t", "--task", metavar="NAME", | parser.add_argument("-t", "--task", metavar="NAME", | ||||
| help="given the name of a task, the bot will run it instead of the main bot and then exit") | |||||
| help="""given the name of a task, the bot will run it | |||||
| instead of the main bot and then exit""") | |||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| level = logging.INFO | level = logging.INFO | ||||
| @@ -693,9 +693,8 @@ class Page(CopyrightMixin): | |||||
| """Add a new section to the bottom of the page. | """Add a new section to the bottom of the page. | ||||
| The arguments for this are the same as those for :py:meth:`edit`, but | The arguments for this are the same as those for :py:meth:`edit`, but | ||||
| instead of providing a summary, you provide a section title. | |||||
| Likewise, raised exceptions are the same as :py:meth:`edit`'s. | |||||
| instead of providing a summary, you provide a section title. Likewise, | |||||
| raised exceptions are the same as :py:meth:`edit`'s. | |||||
| This should create the page if it does not already exist, with just the | This should create the page if it does not already exist, with just the | ||||
| new section as content. | new section as content. | ||||
| @@ -646,11 +646,10 @@ class Site(object): | |||||
| If *all* is ``False`` (default), we'll return the first name in the | If *all* is ``False`` (default), we'll return the first name in the | ||||
| list, which is usually the localized version. Otherwise, we'll return | list, which is usually the localized version. Otherwise, we'll return | ||||
| the entire list, which includes the canonical name. | |||||
| For example, this returns ``u"Wikipedia"`` if *ns_id* = ``4`` and | |||||
| *all* = ``False`` on ``enwiki``; returns ``[u"Wikipedia", u"Project", | |||||
| u"WP"]`` if *ns_id* = ``4`` and *all* is ``True``. | |||||
| the entire list, which includes the canonical name. For example, this | |||||
| returns ``u"Wikipedia"`` if *ns_id* = ``4`` and *all* is ``False`` on | |||||
| ``enwiki``; returns ``[u"Wikipedia", u"Project", u"WP"]`` if *ns_id* = | |||||
| ``4`` and *all* is ``True``. | |||||
| Raises :py:exc:`~earwigbot.exceptions.NamespaceNotFoundError` if the ID | Raises :py:exc:`~earwigbot.exceptions.NamespaceNotFoundError` if the ID | ||||
| is not found. | is not found. | ||||