Beispiel #1
0
    def test_peal_speed_parsing(self):
        test_cases = [("3h04m", 184), ("10", 10), ("3m", 3), ("3h", 180),
                      ("3 h", 180), ("2h58", 178), ("2h58m", 178),
                      (" 2 h 30 m ", 150)]

        for (input_arg, expected_minutes) in test_cases:
            with self.subTest(input=input_arg,
                              expected_minutes=expected_minutes):
                self.assertEqual(expected_minutes, parse_peal_speed(input_arg))
Beispiel #2
0
    def test_peal_speed_parsing_errors(self):
        test_cases = [("3h4hhh", "The peal speed should contain at most one 'h'."),
                      ("Xh", "The hour value 'X' is not an integer."),
                      ("-4h", "The hour value '-4' must be a positive integer."),
                      ("3hP", "The minute value 'P' is not an integer."),
                      ("3h-2m", "The minute value '-2' must be a positive integer."),
                      ("3h100", "The minute value '100' must be smaller than 60."),
                      ("125x", "The minute value '125x' is not an integer."),
                      ("-2m", "The minute value '-2' must be a positive integer."),
                      ("-200", "The minute value '-200' must be a positive integer."),
                      ("", "The minute value '' is not an integer."),
                      ("    ", "The minute value '' is not an integer."),
                      ("\nXX   X ", "The minute value 'XX   X' is not an integer.")]

        for (input_arg, expected_message) in test_cases:
            with self.subTest(input=input_arg, expected_message=expected_message):
                with self.assertRaises(PealSpeedParseError) as e:
                    parse_peal_speed(input_arg)
                self.assertEqual(expected_message, e.exception.message)
Beispiel #3
0
def console_main(override_args: Optional[List[str]],
                 stop_on_join_tower: bool) -> None:
    """
    The main function of Wheatley, when called from the Command Line.
    This parses the CLI arguments, creates the Rhythm, RowGenerator and Bot objects, then starts
    Wheatley's mainloop.

    The two optional arguments are used by the integration tester to give Wheatley artificial argument values
    (override_args) and make Wheatley exit with error code 0 on joining a tower so that hanging forever
    can be differentiated from Wheatley's normal behaviour of sitting in an infinite loop waiting for input.
    """
    __version__ = get_version_number()

    # PARSE THE ARGUMENTS

    parser = argparse.ArgumentParser(
        description="A bot to fill in bells during ringingroom.com practices")

    # Tower arguments
    tower_group = parser.add_argument_group("Tower arguments")
    tower_group.add_argument(
        "room_id",
        type=int,
        help=
        "The numerical ID of the tower to join, represented as a row on 9 bells, \
              e.g. 763451928.")
    tower_group.add_argument(
        "--url",
        default="https://ringingroom.com",
        type=str,
        help=
        "The URL of the server to join (defaults to 'https://ringingroom.com')"
    )
    tower_group.add_argument(
        "--name",
        default=None,
        type=str,
        help="If set, then Wheatley will ring bells assigned to the given name. \
             When not set, Wheatley rings unassigned bells.")

    # Row generation arguments
    row_gen_group = parser.add_argument_group("Row generation arguments")

    # An mutual exclusion group to disallow specifying both a method *and* a CompLib comp
    comp_method_group = row_gen_group.add_mutually_exclusive_group(
        required=True)
    comp_method_group.add_argument(
        "-c",
        "--comp",
        type=str,
        help="The ID or URL of the complib composition you want to ring")
    comp_method_group.add_argument(
        "-m",
        "--method",
        type=str,
        help="The title of the method you want to ring")

    row_gen_group.add_argument(
        "-b",
        "--bob",
        default="14",
        help=
        'An override for what place notation(s) should be made when a `Bob` is called in \
              Ringing Room.  These will by default happen at the lead end.  Examples: "16" or \
              "0:16" => 6ths place lead end bob.  "-1:3" or "-1:3.1" => a Grandsire Bob.  "20: 70" \
              => a 70 bob taking effect 20 changes into a lead (the Half Lead for Surprise Royal). \
              "20:7/0:4" => a 70 bob 20 changes into a lead and a 14 bob at the lead end. \
              "3: 5/9: 5" => bobs in Stedman Triples.  Defaults to "14".')
    row_gen_group.add_argument(
        "-n",
        "--single",
        default="1234",
        help=
        'An override for what place notation(s) should be made when a `Single` is called in \
              Ringing Room.  These will by default happen at the lead end.  Examples: "1678" or \
              "0:1678" => 6ths place lead end single.  "-1:3.123" => a Grandsire Single. \
              "20: 7890" => a 7890 single taking effect 20 changes into a lead (the Half Lead for \
              Surprise Royal). "3: 567/9: 567" => singles in Stedman Triples.  Defaults to "1234".'
    )
    row_gen_group.add_argument(
        "--start-index",
        type=int,
        default=0,
        help=
        "Determines which row of the lead Wheatley will start ringing.  This can be negative (so -1 \
              would refer to the lead **end**).  Defaults to 0 (i.e. a standard start)."
    )
    row_gen_group.add_argument(
        "-u",
        "--use-up-down-in",
        action="store_true",
        help=
        "If set, then the Wheatley will automatically go into changes after two rounds have been \
              rung.")
    row_gen_group.add_argument(
        "-s",
        "--stop-at-rounds",
        action="store_true",
        help=
        "If set, then Wheatley will stand his bells the first time rounds is reached."
    )
    row_gen_group.add_argument(
        "-H",
        "--handbell-style",
        action="store_true",
        help=
        "If set, then Wheatley will ring 'handbell style', i.e. ringing two strokes of \
              rounds then straight into changes, and stopping at the first set of rounds. By \
              default, he will ring 'towerbell style', i.e. only taking instructions from the \
              ringing-room calls. This is equivalent to using the '-us' flags."
    )
    row_gen_group.add_argument(
        "--no-calls",
        action="store_true",
        help=
        "If set, Wheatley will not call anything when ringing compositions.")

    # Rhythm arguments
    rhythm_group = parser.add_argument_group("Rhythm arguments")
    rhythm_group.add_argument(
        "-k",
        "--keep-going",
        action="store_true",
        help=
        "If set, Wheatley will not wait for users to ring - instead, he will push on with the \
              rhythm.")
    rhythm_group.add_argument(
        "-w",
        "--wait",
        action="store_true",
        help=
        "Legacy parameter, which is now set by default. The previous default behaviour of not waiting \
              can be set with '-k'/'--keep-going'.")
    rhythm_group.add_argument(
        "-I",
        "--inertia",
        type=float,
        default=0.5,
        help=
        "Overrides Wheatley's 'inertia' - how much Wheatley will take other ringers' positions \
              into account when deciding when to ring.  0.0 means he will cling as closely as \
              possible to the current rhythm, 1.0 means that he will completely ignore the other \
              ringers.")
    rhythm_group.add_argument(
        "-S",
        "--peal-speed",
        default="2h58",
        help=
        "Sets the default speed that Wheatley will ring (assuming a peal of 5040 changes), \
              though this will usually be adjusted by Wheatley whilst ringing to keep with other \
              ringers.  Example formatting: '3h4' = '3h4m' = '3h04m' = '3h04' = '184m' = '184'. \
              Defaults to '2h58'.")
    rhythm_group.add_argument(
        "-G",
        "--handstroke-gap",
        type=float,
        default=1.0,
        help=
        "Sets the handstroke gap as a factor of the space between two bells.  Defaults to \
              '1.0'.")
    rhythm_group.add_argument(
        "-X",
        "--max-bells-in-dataset",
        type=int,
        default=15,
        help=
        "Sets the maximum number of bells that Wheatley will store to determine the current \
              ringing speed.  If you make this larger, then he will be more consistent but less \
              quick to respond to changes in rhythm.  Setting both this and \
              --inertia to a very small values could result in Wheatley ringing ridiculously \
              quickly.  Defaults to '15'.")

    # Misc arguments
    parser.add_argument("--version",
                        action="version",
                        version=f"Wheatley {__version__}")
    parser.add_argument("-v",
                        "--verbose",
                        action="count",
                        default=0,
                        help="Makes Wheatley print more (DEBUG) output.")
    parser.add_argument(
        "-q",
        "--quiet",
        action="count",
        default=0,
        help=
        "Makes Wheatley print less output.  `-q` only prints WARNINGs and ERRORs; `-qq` only prints \
              ERRORs; `-qqq` prints nothing.")

    # Parse arguments
    # `[1:]` is apparently needed, because sys.argv[0] is the working file of the Python interpreter
    # which `parser.parse_args` does not want to see as an argument
    args = parser.parse_args(
        sys.argv[1:] if override_args is None else override_args)

    # Deprecation warnings
    if args.wait:
        print(
            "Deprecation warning: `--wait` has been replaced with `--keep-going`!"
        )

    # Run the program
    configure_logging(args.verbose, args.quiet)

    try:
        tower_url = get_load_balancing_url(args.room_id, args.url)
    except TowerNotFoundError as e:
        sys.exit(f"Bad value for 'room_id': {e}")
    except InvalidURLError as e:
        sys.exit(f"Bad value for '--url': {e}")

    tower = RingingRoomTower(args.room_id, tower_url)
    row_generator = create_row_generator(args)

    try:
        peal_speed = parse_peal_speed(args.peal_speed)
    except PealSpeedParseError as e:
        sys.exit(f"{e}")

    rhythm = create_rhythm(peal_speed, args.inertia, args.max_bells_in_dataset,
                           args.handstroke_gap, not args.keep_going)
    bot = Bot(tower,
              row_generator,
              args.use_up_down_in or args.handbell_style,
              args.stop_at_rounds or args.handbell_style,
              not args.no_calls,
              rhythm,
              user_name=args.name)

    # Catch keyboard interrupts and just print 'Bye!' instead a load of guff
    try:
        with tower:
            tower.wait_loaded()
            if not stop_on_join_tower:
                bot.main_loop()
    except KeyboardInterrupt:
        print("Bye!")