A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot

158 lines
5.8 KiB

  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Copyright (C) 2009-2014 Ben Kurtovic <ben.kurtovic@gmail.com>
  5. #
  6. # Permission is hereby granted, free of charge, to any person obtaining a copy
  7. # of this software and associated documentation files (the "Software"), to deal
  8. # in the Software without restriction, including without limitation the rights
  9. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. # copies of the Software, and to permit persons to whom the Software is
  11. # furnished to do so, subject to the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be included in
  14. # all copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFTWARE.
  23. """
  24. usage: :command:`earwigbot [-h] [-v] [-d | -q] [-t NAME] [PATH] ...`
  25. This is EarwigBot's command-line utility, enabling you to easily start the bot
  26. or run specific tasks.
  27. .. glossary::
  28. ``PATH``
  29. path to the bot's working directory, which will be created if it doesn't
  30. exist; current directory assumed if not specified
  31. ``-h``, ``--help``
  32. show this help message and exit
  33. ``-v``, ``--version``
  34. show program's version number and exit
  35. ``-d``, ``--debug``
  36. print all logs, including ``DEBUG``-level messages
  37. ``-q``, ``--quiet``
  38. don't print any logs except warnings and errors
  39. ``-t NAME``, ``--task NAME``
  40. given the name of a task, the bot will run it instead of the main bot and
  41. then exit
  42. ``TASK_ARGS``
  43. with --task, will pass any remaining arguments to the task's
  44. :py:meth:`.Task.run` method
  45. """
  46. from argparse import Action, ArgumentParser, REMAINDER
  47. import logging
  48. from os import path
  49. from time import sleep
  50. from earwigbot import __version__
  51. from earwigbot.bot import Bot
  52. __all__ = ["main"]
  53. class _StoreTaskArg(Action):
  54. """A custom argparse action to read remaining command-line arguments."""
  55. def __call__(self, parser, namespace, values, option_string=None):
  56. kwargs = {}
  57. name = None
  58. for value in values:
  59. if value.startswith("-") and "=" in value:
  60. key, value = value.split("=", 1)
  61. self.insert(kwargs, key.lstrip("-"), value)
  62. elif name:
  63. if value.startswith("-"):
  64. if name not in kwargs:
  65. kwargs[name] = True
  66. name = value.lstrip("-")
  67. else:
  68. self.insert(kwargs, name, value)
  69. name = None
  70. else:
  71. if value.startswith("-"):
  72. name = value.lstrip("-")
  73. if name and name not in kwargs:
  74. kwargs[name] = True
  75. namespace.task_args = kwargs
  76. def insert(self, kwargs, key, value):
  77. """Add a key/value pair to kwargs; support multiple values per key."""
  78. if key in kwargs:
  79. try:
  80. kwargs[key].append(value)
  81. except AttributeError:
  82. kwargs[key] = [kwargs[key], value]
  83. else:
  84. kwargs[key] = value
  85. def main():
  86. """Main entry point for the command-line utility."""
  87. version = "EarwigBot v{0}".format(__version__)
  88. desc = """This is EarwigBot's command-line utility, enabling you to easily
  89. start the bot or run specific tasks."""
  90. parser = ArgumentParser(description=desc)
  91. parser.add_argument("path", nargs="?", metavar="PATH", default=path.curdir,
  92. help="""path to the bot's working directory, which will
  93. be created if it doesn't exist; current
  94. directory assumed if not specified""")
  95. parser.add_argument("-v", "--version", action="version", version=version)
  96. logger = parser.add_mutually_exclusive_group()
  97. logger.add_argument("-d", "--debug", action="store_true",
  98. help="print all logs, including DEBUG-level messages")
  99. logger.add_argument("-q", "--quiet", action="store_true",
  100. help="don't print any logs except warnings and errors")
  101. parser.add_argument("-t", "--task", metavar="NAME",
  102. help="""given the name of a task, the bot will run it
  103. instead of the main bot and then exit""")
  104. parser.add_argument("task_args", nargs=REMAINDER, action=_StoreTaskArg,
  105. metavar="TASK_ARGS",
  106. help="""with --task, will pass these arguments to the
  107. task's run() method""")
  108. args = parser.parse_args()
  109. if not args.task and args.task_args:
  110. unrecognized = " ".join(args.task_args)
  111. parser.error("unrecognized arguments: {0}".format(unrecognized))
  112. level = logging.INFO
  113. if args.debug:
  114. level = logging.DEBUG
  115. elif args.quiet:
  116. level = logging.WARNING
  117. print version
  118. print
  119. bot = Bot(path.abspath(args.path), level=level)
  120. if args.task:
  121. thread = bot.tasks.start(args.task, **args.task_args)
  122. if not thread:
  123. return
  124. try:
  125. while thread.is_alive(): # Keep it alive; it's a daemon
  126. sleep(1)
  127. except KeyboardInterrupt:
  128. pass
  129. finally:
  130. if thread.is_alive():
  131. bot.tasks.logger.warn("The task will be killed")
  132. else:
  133. try:
  134. bot.run()
  135. except KeyboardInterrupt:
  136. pass
  137. finally:
  138. if bot.is_running:
  139. bot.stop()
  140. if __name__ == "__main__":
  141. main()