示例#1
0
    def test_exhaustive_roundtrip(self) -> None:
        for example in variations("sect", "a", "1"):
            oracle = configparser.RawConfigParser()
            try:
                oracle.read_string(example)
                self.assertEqual(oracle["sect"]["a"], "1")
            except Exception:  # pragma: no cover
                continue

            conf = None
            try:
                conf = imperfect.parse_string(example)
                # Did we parse the same value as configparser
                self.assertEqual(len(conf["sect"].entries), 1)
                self.assertEqual(conf["sect"].entries[0].key, "a")
                self.assertEqual(conf["sect"].entries[0].interpret_value(), "1")
            except Exception:  # pragma: no cover
                print("Input: ", repr(example))
                if conf:
                    # if conf.default:
                    #     print("default:")
                    #     for v in conf.default.entries:
                    #         print("  ", v)
                    for s in conf.sections:
                        print(f"{s.name}:")
                        for v in s.entries:
                            print("  ", v)
                raise

            self.assertEqual(example, conf.text)
示例#2
0
def from_setup_cfg(path: Path, markers: Dict[str, Any]) -> Distribution:

    cfg = imperfect.parse_string((path / "setup.cfg").read_text())

    d = Distribution()
    d.metadata_version = "2.1"

    for field in SETUP_ARGS:
        name = field.get_distribution_key()
        if not hasattr(d, name):
            continue

        cls = field.cfg.writer_cls
        if cls is SectionWriter:
            try:
                raw_section_data = cfg[field.cfg.section]
            except KeyError:
                continue
            # ConfigSection behaves like a Dict[str, str] so this is fine
            parsed = SectionWriter().from_ini_section(
                raw_section_data)  # type: ignore
        else:
            try:
                # All fields are defined as underscore, but it appears
                # setuptools normalizes so dashes are ok too.
                key = field.cfg.key
                if key not in cfg[field.cfg.section]:
                    key = key.replace("_", "-")
                raw_data = cfg[field.cfg.section][key]
            except KeyError:
                continue
            parsed = cls().from_ini(raw_data)

        setattr(d, name, parsed)
    return d
示例#3
0
    def test_example_from_readme(self) -> None:
        INPUT = """
[metadata]
# the package name
name = imperfect
# slurp the readme
long_description = file: README.md

[options]
packages = imperfect
"""
        EXPECTED = """
[metadata]
# the package name
name = imperfect
# slurp the readme
long_description = file: README.md
long_description_content_type =  text/markdown

[options]
packages = imperfect
"""
        import imperfect
        import moreorless

        data = INPUT

        conf: imperfect.ConfigFile = imperfect.parse_string(data)
        metadata_section = conf["metadata"]

        # Ignoring some whitespace for now, this looks like
        # long_description_content_type =  text/markdown\n
        # [                   entry                      ]
        # [            key            ][eq][    value    ]

        value = imperfect.ValueLine(
            whitespace_before_text="",
            text="text/markdown",
            whitespace_after_text="",
            newline="\n",
        )
        new_entry = imperfect.ConfigEntry(
            key="long_description_content_type",
            equals="=",
            value=[value],
            whitespace_before_equals=" ",
            whitespace_before_value="  ",
        )
        for i, entry in enumerate(metadata_section.entries):
            if entry.key == "long_description":
                metadata_section.entries.insert(i + 1, new_entry)
                break

        self.assertEqual(EXPECTED, conf.text)

        temp = moreorless.unified_diff(data, conf.text, "setup.cfg")
        # print(temp, end="")
        self.assertTrue(temp)
示例#4
0
    def test_simple_parse(self, example: str) -> None:
        conf = imperfect.parse_string(example)
        # Minimal mapping api
        self.assertEqual(["s"], conf.keys())
        self.assertTrue("s" in conf)
        self.assertEqual(["a"], conf["s"].keys())
        self.assertTrue("a" in conf["s"])

        # KeyError coverage
        self.assertFalse("b" in conf)
        self.assertFalse("b" in conf["s"])

        self.assertIn(conf["s"]["a"], ("1", "\n1"))
示例#5
0
    def check(self, env: Env, autoapply: bool = False) -> None:
        tox_ini_path = env.base_path / "tox.ini"
        setup_cfg_path = env.base_path / "setup.cfg"

        if not tox_ini_path.exists():
            return

        tox_ini_text = tox_ini_path.read_text()
        setup_cfg_text = ""
        if setup_cfg_path.exists():
            setup_cfg_text = setup_cfg_path.read_text()

        setup_cfg = parse_string(setup_cfg_text)
        tox_ini = parse_string(tox_ini_text)

        for section in tox_ini.keys():
            # This doesn't iterate and use set_value because that wouldn't
            # preserve comments.
            obj = tox_ini[section]
            if obj.name == "tox":
                obj.name = "tox:tox"

            if obj.name in setup_cfg:
                LOG.error(f"Section {obj.name!r} already exists in setup.cfg")
            else:
                if obj.leading_whitespace == "" and setup_cfg.sections:
                    obj.leading_whitespace = "\n"
                setup_cfg.sections.append(obj)

        new_text = setup_cfg.text
        if new_text != setup_cfg_text:
            echo_color_unified_diff(setup_cfg_text, new_text, "setup.cfg")
            if autoapply:
                setup_cfg_path.write_text(new_text)
                tox_ini_path.unlink()
                print("Written")
            else:
                print("Rerun with -a instead to apply")
示例#6
0
def verify(name: str) -> None:
    with open(name) as f:
        data = f.read()
    conf = parse_string(data)
    buf = io.StringIO()
    conf.build(buf)
    if data != buf.getvalue():
        print(name, "FAIL ROUND TRIP")
        return

    try:
        co = configparser.RawConfigParser()
        co.read_string(data)
    except Exception as e:
        print(name, "FAIL TO READ IN CONFIGPARSER", e)
        return
    compares = 0
    for section_name in co:
        for key in co[section_name]:
            compares += 1
            expected_value = co[section_name][key]
            try:
                if conf[section_name][key] != expected_value:
                    print(name, section_name, key, "FAIL COMPARE")
                    print("configpar:", repr(expected_value))
                    print("imperfect:", repr(conf[section_name][key]))
                    return
            except Exception as e:
                print(name, section_name, key, "FAIL", str(e))
                return

    if compares == 0:
        print(name, "FAIL EMPTY")
        return

    print(name, "OK")
    def check(self, env: Env, autoapply: bool = False) -> None:
        # TODO: This duplicates some of the logic in
        # setup_py_parsing because we don't want a Distribution and
        # want to get the cst object back.

        setup_py = env.base_path / "setup.py"
        if not setup_py.exists():
            return
        module = cst.parse_module(setup_py.read_text())
        analyzer = SetupCallAnalyzer()
        wrapper = cst.MetadataWrapper(module)
        wrapper.visit(analyzer)

        if not analyzer.found_setup:
            self.skip("Could not find setup() call in setup.py")

        setup_cfg = env.base_path / "setup.cfg"
        if setup_cfg.exists():
            cfg_data = setup_cfg.read_text()
        else:
            cfg_data = ""

        cfg = imperfect.parse_string(
            cfg_data + ("\n" if not cfg_data.endswith("\n") else ""))
        setup_args_dict = {t.keyword: t for t in SETUP_ARGS}
        keywords_to_change: Dict[str, Optional[cst.CSTNode]] = {}
        for k, v in analyzer.saved_args.items():
            # This is a cursory check on the value; really we should get an
            # AlmostTooComplicated or TooComplicated that works well.
            if (k in setup_args_dict and isinstance(v, Literal)
                    and "??" not in str(v.value)):
                LOG.debug(f"{k}: can move {v.value!r}")
                keywords_to_change[k] = None
                if setup_args_dict[k].cfg.writer_cls is SectionWriter:
                    for k2, v2 in v.value.items():
                        cfg.set_value(
                            setup_args_dict[k].cfg.section,
                            k2,
                            setup_args_dict[k].cfg.writer_cls().to_ini(v2),
                        )
                else:
                    cfg.set_value(
                        setup_args_dict[k].cfg.section,
                        setup_args_dict[k].cfg.key,
                        setup_args_dict[k].cfg.writer_cls().to_ini(v.value),
                    )
            elif k in setup_args_dict:  # pragma: no cover
                LOG.error(f"{k}: too complicated")
            else:  # pragma: no cover
                LOG.debug(f"{k}: unknown keyword")

        buf = io.StringIO()
        cfg.build(buf)

        echo_color_unified_diff(cfg_data, buf.getvalue(), "setup.cfg")

        old_code = module.code
        transformer = SetupCallTransformer(
            cst.ensure_type(analyzer.setup_node, cst.Call), keywords_to_change)
        new_module = wrapper.visit(transformer)
        new_code = new_module.code

        echo_color_unified_diff(old_code, new_code, "setup.py")

        # TODO validate that the right args got removed
        # print(f"And remove {sorted(keywords_to_change)}")

        if autoapply:
            setup_cfg.write_text(buf.getvalue())
            setup_py.write_text(new_code)
        elif keywords_to_change:
            print("Rerun with -a instead to apply")
示例#8
0
 def test_simple_roundtrips(self, example: str) -> None:
     conf = imperfect.parse_string(example)
     self.assertEqual(example, conf.text)
示例#9
0
 def test_comment_prefixes(self) -> None:
     conf = imperfect.parse_string("[s]\n@comment\na=1", comment_prefixes=("@",))
     self.assertEqual("1", conf["s"]["a"])
示例#10
0
 def test_alternate_delimiters_allow_no_value(self) -> None:
     conf = imperfect.parse_string(
         "[s]\naqq", delimiters=("qq",), allow_no_value=True
     )
     self.assertEqual("", conf["s"]["a"])
示例#11
0
 def test_alternate_delimiters(self) -> None:
     conf = imperfect.parse_string("[s]\naqq1", delimiters=("qq",))
     self.assertEqual("1", conf["s"]["a"])
示例#12
0
 def test_allow_no_value(self) -> None:
     conf = imperfect.parse_string("[s]\na=", allow_no_value=True)
     self.assertEqual("", conf["s"]["a"])
示例#13
0
 def test_multiline_with_comment(self) -> None:
     conf = imperfect.parse_string("[s]\na=\n #comment\n b\n")
     self.assertEqual("\nb", conf["s"]["a"])
示例#14
0
 def test_fail_to_parse(self, example: str) -> None:
     with self.assertRaises(imperfect.ParseError):
         imperfect.parse_string(example)