text: from io import StringIO
  from fractions import Fraction
  from itertools import cycle
  from operator import itemgetter
  from pathlib import Path
  import pickle
  from random import choices
  
  from cytoolz import assoc, groupby, valmap
  import pytest
  
  from bourbaki.application.config import load_config
  from bourbaki.application.cli.main import OPTIONAL_ARG_TEMPLATE
  from bourbaki.application.typed_io.utils import *
  from bourbaki.application.typed_io.cli_repr_ import bool_cli_repr
  from bourbaki.introspection.callables import is_classmethod, is_staticmethod
  
  DIR = Path(__file__).parent.absolute()
  sys.path.insert(0, str(DIR))
  from cli import cli, MyCommandLineApp, FooEnum
  
  
  def maybe(key):
      return OPTIONAL_ARG_TEMPLATE.format(key)
  
  
  output_args = {
      "pretty": False,
      "literal": False,
      maybe("outfile"): text_path_repr,
  }
  
  fooenum_repr = "|".join(v.name for v in FooEnum)
  
  today = datetime.date.today().isoformat()
  
  CONFIG_FILE = str(DIR / "conf.yml")
  
  CONFIG = {
      "__init__": {"a": 1, "b": [1, 2, 3], maybe("c"): date_repr},
      "print": {
          "ns": {**output_args},
          "tuple": {
              "kwarg": {
                  "tup": [
                      type_spec(int),
                      type_spec(int) + " OR " + fraction_repr,
                      type_spec(float),
                  ],
                  **output_args,
              },
              "pos": {
                  "tup": [
                      type_spec(int),
                      type_spec(int) + " OR " + fraction_repr,
                      type_spec(float),
                  ],
                  **output_args,
              },
              "union-kwarg": {
                  "tup": [
                      [type_spec(int) + " OR " + fraction_repr, type_spec(str)],
                      "OR",
                      [type_spec(str), type_spec(bool)],
                  ],
                  **output_args,
              },
          },
          "enum": {maybe("foo"): fooenum_repr, **output_args},
          "bytes": {"b": [byte_repr, ellipsis_], **output_args},
          "flags": {
              "boolean1": bool_cli_repr,
              "boolean2": False,
              "flag1": False,
              "flag2": True,
          },
          "mapping": {
              "foo_to_date": {fooenum_repr: date_repr, ellipsis_: ellipsis_},
              **output_args,
          },
          "named-tuple": {
              "tup": {"foo": FooEnum.foo.name, "bar": ".", "baz": today},
              **output_args,
          },
          "numbers": {
              maybe("x"): decimal_repr,
              maybe("y"): fraction_repr,
              maybe("z"): complex_repr,
              **output_args,
          },
          "url": {"url": url_repr, **output_args},
          "uuid": {maybe("uuid"): uuid_repr, **output_args},
      },
      "args": {"args": [fooenum_repr, ellipsis_], **output_args},
      "args-and-kwargs": {
          "ips": [ipv6_repr, "..."],
          **output_args,
          "named_ips": {type_spec(str): ipv4_repr, ellipsis_: ellipsis_},
      },
      "types": {
          "number": classpath_type_repr + "<:Union[int,fractions.Fraction]",
          **output_args,
          "types": {
              type_spec(str): classpath_type_repr + "<:Mapping",
              ellipsis_: ellipsis_,
          },
      },
      "leading": {
          "list": {"l_1": [float_repr, ellipsis_], "i_2": int_repr, **output_args}
      },
      "cant": {
          "parse": {
              maybe("cant_parse_me"): [[type_spec(str), ellipsis_], ellipsis_],
              "can_parse_me": [type_spec(str), ellipsis_],
              **output_args,
          }
      },
      "get": {"attr": {"attr": type_spec(str), **output_args}},
  }
  
  
  def keys_recursive(dict_: dict):
      def inner(dict_, prefix):
          recurse = all(isinstance(v, dict) for v in dict_.values())
          if recurse:
              for k, v in dict_.items():
                  yield from inner(v, (*prefix, k))
          else:
              yield prefix
  
      return list(t for t in inner(dict_, ()) if not t[0].startswith("__"))
  
  
  def getrecursive(dict_, keys):
      if not any(keys):
          return dict_
      head_to_tails = valmap(
          lambda l: [t[1:] for t in l], groupby(itemgetter(0), filter(len, keys))
      )
      return {
          head: getrecursive(dict_[head], tails) for head, tails in head_to_tails.items()
      }
  
  
  COMMANDS = keys_recursive(CONFIG)
  
  RANDOM_COMMANDS = [set(choices(COMMANDS, k=3)) for _ in range(10)]
  
  fmts = ["py", "yaml", "json"]
  
  
  # currently can't run this because lookup of classes defined in cli.py fails
  # @pytest.fixture(scope='module')
  # def cli_pickle_reloaded():
  #     f = io.BytesIO()
  #     pickle.dump(cli, f)
  #     f.seek(0)
  #     return pickle.load(f)
  
  
  def test_command_names():
      fns = (
          getattr(v, "__command_prefix__", (n.replace("_", "-"),))[0]
          for n, v in MyCommandLineApp.__dict__.items()
          if callable(v)
          and not n.startswith("_")
          and not is_classmethod(MyCommandLineApp, n)
          and not is_staticmethod(MyCommandLineApp, n)
      )
      assert set(cli.subcommands) == set(fns).union(["init"])
  
  
  # TODO: more tests
  @pytest.mark.parametrize(
      "args, result, config_format",
      [
          (
              ["--config", CONFIG_FILE, "print", "ns"],
              {"a": 1, "b": [1, 2, 3], "c": datetime.date.today()},
              None,
          ),
          (
              ["--config", CONFIG_FILE, "print", "tuple", "kwarg"],
              (12345678, Fraction(1234, 5678), 1234.5678),
              None,
          ),
          (
              ["print", "tuple", "kwarg", "--tup", "123", "456", "7.89"],
              (123, 456, 7.89),
              None,
          ),
          *[(["init", "config", "--format", fmt], CONFIG, "." + fmt) for fmt in fmts],
          *[
              (
                  [
                      "init",
                      "config",
                      "--format",
                      fmt,
                      "--only-commands",
                      *map(" ".join, cmds),
                  ],
                  assoc(getrecursive(CONFIG, cmds), "__init__", CONFIG["__init__"]),
                  "." + fmt,
              )
              for cmds, fmt in zip(RANDOM_COMMANDS, cycle(fmts))
          ],
      ],
  )
  def test_cli_result(args, result, config_format, capsys):
      cli_result = cli.run(args)
      assert cli_result == result
      if config_format:
          out = capsys.readouterr().out
          parsed = load_config(StringIO(out), ext=config_format)
          assert parsed == result
  
  
  @pytest.mark.parametrize("args,exc", [
      (['--install-bash-completion'], SystemExit),
      (['--version'], SystemExit),
      (["--info"], SystemExit),
      (['--execute', 'print', 'ns'], None),
  ])
  def test_cli_special_actions(args, exc):
      if exc is None:
          cli.run(args)
      else:
          with pytest.raises(exc):
              cli.run(args)
  
  
  def test_cli_parses_env(monkeypatch):
      monkeypatch.setenv('CLI_ARG_B', '"1/2" 34 \'56\'')
      ns = cli.run(['print', 'ns'])
      assert ns['b'] == [Fraction(1, 2), 34, 56]
      monkeypatch.delenv('CLI_ARG_B')
  