コード例 #1
0
    def test_version_note_tuple(self):
        """Test the version note tuple."""
        parser = ChangelogParser(parent_folder="test/fixtures")

        version = parser._version_note("0.0.0")
        self.assertIsNone(version)

        version = parser._version_note("10.1.0-beta1")
        self.assertEqual("10", version.major)
        self.assertEqual("1", version.minor)
        self.assertEqual("0", version.patch)
        self.assertEqual("", version.url)
        self.assertEqual("beta1", version.prerelease)
        self.assertTrue(version.is_prerelease)
        self.assertEqual("",
                         version.separator)  # Not sure what is the separator
        self.assertEqual("2021/02/08", version.date)
        self.assertEqual(
            ("- This is the latest documented version in this changelog\n"
             "- The changelog module is tested against these lines\n"
             "- Be careful modifying this file"),
            version.text,
        )

        version = parser._version_note("10.0.1")
        self.assertEqual("", version.prerelease)
        self.assertFalse(version.is_prerelease)
コード例 #2
0
    def test_changelog_content_ci_fake(self):
        """Test specific fake version used in tests."""
        parser = ChangelogParser()
        fake_version_content = parser.content(tag="0.1.2")

        # expected result
        expected = ("(This version note is used in unit-tests)\n\n"
                    '- Tag without "v" prefix\n'
                    "- Add a CHANGELOG.md file for testing")

        self.assertIsInstance(fake_version_content, str)
        self.assertEqual(expected, fake_version_content)
コード例 #3
0
 def test_changelog_latest(self):
     """Test against the latest special option value. \
     See: https://github.com/opengisch/qgis-plugin-ci/pull/33
     """
     self.assertTrue(ChangelogParser.has_changelog())
     parser = ChangelogParser(CHANGELOG_REGEXP)
     expected_latest = (
         "* Tag using a wrong format DD/MM/YYYY according to Keep A Changelog\n"
         '* Tag without "v" prefix\n'
         "* Add a CHANGELOG.md file for testing")
     print(parser.content("latest"))
     self.assertEqual(expected_latest, parser.content("latest"))
コード例 #4
0
    def test_changelog_content_latest(self):
        """Test against the latest special option value. \
        See: https://github.com/opengisch/qgis-plugin-ci/pull/33
        """
        # expected result
        expected_latest = (
            "- This is the latest documented version in this changelog\n"
            "- The changelog module is tested against these lines\n"
            "- Be careful modifying this file")

        # get latest
        parser = ChangelogParser(parent_folder="test/fixtures")
        self.assertEqual(expected_latest, parser.content("latest"))

        self.assertEqual("10.1.0-beta1", parser.latest_version())
コード例 #5
0
    def test_release_changelog(self):
        """Test if changelog in metadata.txt inside zipped plugin after release command."""
        # variables
        cli_config_changelog = Path(
            "test/fixtures/.qgis-plugin-ci-test-changelog.yaml")
        version_to_release = "0.1.2"

        # load specific parameters
        with cli_config_changelog.open() as in_cfg:
            arg_dict = yaml.safe_load(in_cfg)
        parameters = Parameters(arg_dict)
        self.assertIsInstance(parameters, Parameters)

        # get output zip path
        archive_name = parameters.archive_name(
            plugin_name=parameters.plugin_path,
            release_version=version_to_release)

        # extract last items from changelog
        parser = ChangelogParser()
        self.assertTrue(parser.has_changelog())
        changelog_lastitems = parser.last_items(
            count=parameters.changelog_number_of_entries)

        # Include a changelog
        release(
            parameters=parameters,
            release_version=version_to_release,
            allow_uncommitted_changes=True,
        )

        # open archive and compare
        with ZipFile(archive_name, "r") as zip_file:
            data = zip_file.read(f"{parameters.plugin_path}/metadata.txt")

        # Changelog
        self.assertGreater(
            data.find(bytes(changelog_lastitems, "utf8")),
            0,
            f"changelog detection failed in release: {data}",
        )

        # Commit number
        self.assertEqual(1, len(re.findall(r"commitNumber=\d+", str(data))))

        # Commit sha1 not in the metadata.txt
        self.assertEqual(0, len(re.findall(r"commitSha1=\d+", str(data))))
コード例 #6
0
    def test_changelog_version_note(self):
        """Test version note named tuple structure and mechanisms."""
        # parser
        parser = ChangelogParser(parent_folder="test/fixtures")
        self.assertIsInstance(parser.CHANGELOG_FILEPATH, Path)

        # content parsed
        changelog_content = parser._parse()
        self.assertEqual(len(changelog_content), 7)

        # loop on versions
        for version in changelog_content:
            version_note = VersionNote(*version)
            self.assertIsInstance(version_note.date, str)
            self.assertTrue(hasattr(version_note, "is_prerelease"))
            self.assertTrue(hasattr(version_note, "version"))
            if len(version_note.prerelease):
                self.assertEqual(version_note.is_prerelease, True)
コード例 #7
0
    def test_changelog_content(self):
        """Test version content from changelog."""
        # parser
        parser = ChangelogParser(parent_folder="test/fixtures")
        self.assertIsInstance(parser.CHANGELOG_FILEPATH, Path)

        # Unreleased doesn't count
        self.assertEqual(7, len(parser._parse()))

        # This version doesn't exist
        self.assertIsNone(parser.content("0.0.0"))

        expected_checks = {
            "10.1.0-beta1":
            ("- This is the latest documented version in this changelog\n"
             "- The changelog module is tested against these lines\n"
             "- Be careful modifying this file"),
            "10.1.0-alpha1": (
                "- This is a version with a prerelease in this changelog\n"
                "- The changelog module is tested against these lines\n"
                "- Be careful modifying this file"
                # "\n" TODO Fixed section is missing
                # "- trying with a subsection in a version note"
            ),
            "10.0.1":
            "- End of year version",
            "10.0.0":
            "- A\n- B\n- C",
            "9.10.1":
            "- D\n- E\n- F",
            "v0.1.1":
            ('* Tag with a "v" prefix to check the regular expression\n'
             "* Previous version"),
            "0.1.0":
            "* Very old version",
        }
        for version, expected in expected_checks.items():
            with self.subTest(i=version):
                self.assertEqual(parser.content(version), expected)
コード例 #8
0
    def test_different_changelog_file(self):
        """Test against a different changelog filename."""
        old = Path("test/fixtures/CHANGELOG.md")
        new_folder = Path(tempfile.mkdtemp())
        new_path = new_folder / Path("CHANGELOG-branch-X.md")
        self.assertFalse(new_path.exists())

        new_path.write_text(old.read_text())

        self.assertTrue(
            ChangelogParser.has_changelog(
                parent_folder=new_folder,
                changelog_path=new_path,
            ))
コード例 #9
0
    def test_changelog_last_items(self):
        """Test last items from changelog."""
        # on fixture changelog
        parser = ChangelogParser(parent_folder="test/fixtures")
        last_items = parser.last_items(3)
        self.assertIsInstance(last_items, str)

        # on repository changelog
        parser = ChangelogParser()
        last_items = parser.last_items(3)
        self.assertIsInstance(last_items, str)
コード例 #10
0
    def test_changelog_parser(self):
        """ Test we can parse a changelog with a regex. """
        self.assertTrue(ChangelogParser.has_changelog())
        parser = ChangelogParser(CHANGELOG_REGEXP)
        self.assertIsNone(parser.content("0.0.0"), "")

        expected = (
            "* Tag using a wrong format DD/MM/YYYY according to Keep A Changelog\n"
            '* Tag without "v" prefix\n'
            "* Add a CHANGELOG.md file for testing")
        self.assertEqual(parser.content("0.1.2"), expected)

        expected = (
            "\n "
            "Version 0.1.2 :\n "
            "* Tag using a wrong format DD/MM/YYYY according to Keep A Changelog\n "
            '* Tag without "v" prefix\n '
            "* Add a CHANGELOG.md file for testing\n"
            "\n")
        self.assertEqual(parser.last_items(1), expected)

        expected = """
 Version 0.1.2 :
 * Tag using a wrong format DD/MM/YYYY according to Keep A Changelog
 * Tag without "v" prefix
 * Add a CHANGELOG.md file for testing

 Version v0.1.1 :
 * Tag using a correct format YYYY-MM-DD according to Keep A Changelog
 * Tag with a "v" prefix to check the regular expression
 * Previous version

 Version 0.1.0 :
 * Very old version

"""
        self.assertEqual(parser.last_items(3), expected)
コード例 #11
0
    def test_has_changelog(self):
        """Test changelog path logic."""
        # using this repository as parent folder
        self.assertTrue(ChangelogParser.has_changelog())
        self.assertIsInstance(ChangelogParser.CHANGELOG_FILEPATH, Path)

        # using the fixture subfolder as string
        self.assertTrue(
            ChangelogParser.has_changelog(parent_folder="test/fixtures"))
        self.assertIsInstance(ChangelogParser.CHANGELOG_FILEPATH, Path)

        # using the fixture subfolder as pathlib.Path
        self.assertTrue(
            ChangelogParser.has_changelog(parent_folder=Path("test/fixtures")))
        self.assertIsInstance(ChangelogParser.CHANGELOG_FILEPATH, Path)

        # with a path to a file, must raise a type error
        with self.assertRaises(TypeError):
            ChangelogParser.has_changelog(parent_folder=Path(__file__))
        self.assertIsNone(ChangelogParser.CHANGELOG_FILEPATH, None)

        # with a path to a folder which doesn't exist, must raise a file exists error
        with self.assertRaises(FileExistsError):
            ChangelogParser.has_changelog(parent_folder=Path("imaginary_path"))
コード例 #12
0
def main():
    # create the top-level parser
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("-v",
                        "--version",
                        help="print the version and exit",
                        action="store_true")

    subparsers = parser.add_subparsers(title="commands",
                                       description="qgis-plugin-ci command",
                                       dest="command")

    # package
    package_parser = subparsers.add_parser(
        "package", help="creates an archive of the plugin")
    package_parser.add_argument("release_version",
                                help="The version to be released")
    package_parser.add_argument(
        "--transifex-token",
        help=
        "The Transifex API token. If specified translations will be pulled and compiled.",
    )
    package_parser.add_argument(
        "-u",
        "--plugin-repo-url",
        help=
        "If specified, a XML repository file will be created in the current directory, the zip URL will use this parameter.",
    )
    package_parser.add_argument(
        "-c",
        "--allow-uncommitted-changes",
        action="store_true",
        help=
        "If omitted, uncommitted changes are not allowed before packaging. If specified and some changes are "
        "detected, a hard reset on a stash create will be used to revert changes made by qgis-plugin-ci.",
    )
    package_parser.add_argument(
        "-d",
        "--disable-submodule-update",
        action="store_true",
        help=
        "If omitted, a git submodule is updated. If specified, git submodules will not be updated/initialized before packaging.",
    )

    # changelog
    changelog_parser = subparsers.add_parser("changelog",
                                             help="gets the changelog content")
    changelog_parser.add_argument(
        "release_version",
        help="The version to be released. If nothing is speficied, \
                                      the latest version specified into the changelog is used.",
        default="latest",
    )

    # release
    release_parser = subparsers.add_parser("release",
                                           help="release the plugin")
    release_parser.add_argument("release_version",
                                help="The version to be released (x.y.z).")
    release_parser.add_argument(
        "--release-tag",
        help="The release tag, if different from the version (e.g. vx.y.z).",
    )
    release_parser.add_argument(
        "--transifex-token",
        help=
        "The Transifex API token. If specified translations will be pulled and compiled.",
    )
    release_parser.add_argument(
        "--github-token",
        help=
        "The Github API token. If specified, the archive will be pushed to an already existing release.",
    )
    release_parser.add_argument(
        "--create-plugin-repo",
        action="store_true",
        help=
        "Will create a XML repo as a Github release asset. Github token is required.",
    )
    release_parser.add_argument(
        "--allow-uncommitted-changes",
        action="store_true",
        help=
        "If omitted, uncommitted changes are not allowed before releasing. If specified and some changes are "
        "detected, a hard reset on a stash create will be used to revert changes made by qgis-plugin-ci.",
    )
    release_parser.add_argument(
        "--disable-submodule-update",
        action="store_true",
        help=
        "If omitted, a git submodule is updated. If specified, git submodules will not be updated/initialized before packaging.",
    )
    release_parser.add_argument(
        "--osgeo-username", help="The Osgeo user name to publish the plugin.")
    release_parser.add_argument(
        "--osgeo-password", help="The Osgeo password to publish the plugin.")

    # pull-translation
    pull_tr_parser = subparsers.add_parser(
        "pull-translation", help="pull translations from Transifex")
    pull_tr_parser.add_argument("transifex_token",
                                help="The Transifex API token")
    pull_tr_parser.add_argument("--compile",
                                action="store_true",
                                help="Will compile TS files into QM files")

    # push-translation
    push_tr_parser = subparsers.add_parser(
        "push-translation", help="update strings and push translations")
    push_tr_parser.add_argument("transifex_token",
                                help="The Transifex API token")

    args = parser.parse_args()

    # print the version and exit
    if args.version:
        import pkg_resources

        print("qgis-plugin-ci version: {}".format(
            pkg_resources.get_distribution("qgis-plugin-ci").version))
        parser.exit()

    # if no command is passed, print the help and exit
    if not args.command:
        parser.print_help()
        parser.exit()

    exit_val = 0

    if os.path.isfile(".qgis-plugin-ci"):
        # We read the .qgis-plugin-ci file
        with open(".qgis-plugin-ci", "r", encoding="utf8") as f:
            arg_dict = yaml.safe_load(f)
    else:
        config = configparser.ConfigParser()
        config.read("setup.cfg")
        if "qgis-plugin-ci" in config.sections():
            # We read the setup.cfg file
            arg_dict = dict(config.items("qgis-plugin-ci"))
        else:
            # We don't have either a .qgis-plugin-ci or a setup.cfg
            if args.command == "changelog":
                # but for the "changelog" sub command, the config file is not required, we can continue
                arg_dict = dict()
            else:
                raise ConfigurationNotFound(
                    ".qgis-plugin-ci or setup.cfg with a 'qgis-plugin-ci' section have not been found."
                )

    parameters = Parameters(arg_dict)

    # CHANGELOG
    if args.command == "changelog":
        try:
            c = ChangelogParser(changelog_path=parameters.changelog_path, )
            content = c.content(args.release_version)
            if content:
                print(content)
        except Exception:
            # Better to be safe
            pass

        return exit_val

    # PACKAGE
    if args.command == "package":
        release(
            parameters,
            release_version=args.release_version,
            transifex_token=args.transifex_token,
            allow_uncommitted_changes=args.allow_uncommitted_changes,
            plugin_repo_url=args.plugin_repo_url,
            disable_submodule_update=args.disable_submodule_update,
        )

    # RELEASE
    elif args.command == "release":
        release(
            parameters,
            release_version=args.release_version,
            release_tag=args.release_tag,
            transifex_token=args.transifex_token,
            github_token=args.github_token,
            upload_plugin_repo_github=args.create_plugin_repo,
            osgeo_username=args.osgeo_username,
            osgeo_password=args.osgeo_password,
            allow_uncommitted_changes=args.allow_uncommitted_changes,
            disable_submodule_update=args.disable_submodule_update,
        )

    # TRANSLATION PULL
    elif args.command == "pull-translation":
        t = Translation(parameters, args.transifex_token)
        t.pull()
        if args.compile:
            t.compile_strings()

    # TRANSLATION PUSH
    elif args.command == "push-translation":
        t = Translation(parameters, args.transifex_token)
        t.update_strings()
        t.push()

    return exit_val
コード例 #13
0
ファイル: release.py プロジェクト: phborba/qgis-plugin-ci
def create_archive(
    parameters: Parameters,
    release_version: str,
    archive_name: str,
    add_translations: bool = False,
    allow_uncommitted_changes: bool = False,
    is_prerelease: bool = False,
    raise_min_version: str = None,
    disable_submodule_update: bool = False,
):

    repo = git.Repo()

    top_tar_handle, top_tar_file = mkstemp(suffix=".tar")

    # keep track of current state
    initial_stash = None
    diff = repo.index.diff(None)
    if diff:
        print("Uncommitted changes:")
        for diff in diff:
            print(diff)
        if not allow_uncommitted_changes:
            raise UncommitedChanges(
                "You have uncommitted changes. Stash or commit them or use --allow-uncommitted-changes."
            )
        else:
            initial_stash = repo.git.stash("create")

    # changelog
    if parameters.changelog_include:
        parser = ChangelogParser(parameters.changelog_regexp)
        if parser.has_changelog():
            try:
                content = parser.last_items(
                    parameters.changelog_number_of_entries)
                if content:
                    replace_in_file(
                        "{}/metadata.txt".format(parameters.plugin_path),
                        r"^changelog=.*$",
                        "changelog={}".format(content),
                    )
            except Exception as e:
                # Do not fail the release process if something is wrong when parsing the changelog
                replace_in_file(
                    "{}/metadata.txt".format(parameters.plugin_path),
                    r"^changelog=.*$",
                    "",
                )
                print(
                    "An exception occurred while parsing the changelog file : {}"
                    .format(e))
    else:
        # Remove the changelog line
        replace_in_file("{}/metadata.txt".format(parameters.plugin_path),
                        r"^changelog=.*$", "")

    # set version in metadata
    replace_in_file(
        "{}/metadata.txt".format(parameters.plugin_path),
        r"^version=.*$",
        "version={}".format(release_version),
    )

    # set the plugin as experimental on a pre-release
    if is_prerelease:
        replace_in_file(
            "{}/metadata.txt".format(parameters.plugin_path),
            r"^experimental=.*$",
            "experimental={}".format(True if is_prerelease else False),
        )

    if raise_min_version:
        replace_in_file(
            "{}/metadata.txt".format(parameters.plugin_path),
            r"^qgisMinimumVersion=.*$",
            "qgisMinimumVersion={}".format(raise_min_version),
        )

    # replace any DEBUG=False in all Python files
    if not is_prerelease:
        for file in glob("{}/**/*.py".format(parameters.plugin_path),
                         recursive=True):
            replace_in_file(file, r"^DEBUG\s*=\s*True", "DEBUG = False")

    # keep track of current state
    try:
        stash = repo.git.stash("create")
    except git.exc.GitCommandError:
        stash = "HEAD"
    if stash == "" or stash is None:
        stash = "HEAD"
    # create TAR archive
    print("archive plugin with stash: {}".format(stash))
    repo.git.archive(stash, "-o", top_tar_file, parameters.plugin_path)
    # adding submodules
    for submodule in repo.submodules:
        _, sub_tar_file = mkstemp(suffix=".tar")
        if submodule.path.split("/")[0] != parameters.plugin_path:
            print("skipping submodule not in plugin source directory ({})".
                  format(submodule.name))
            continue
        if not disable_submodule_update:
            submodule.update(init=True)
        sub_repo = submodule.module()
        print("archive submodule:", sub_repo)
        sub_repo.git.archive("HEAD", "--prefix", "{}/".format(submodule.path),
                             "-o", sub_tar_file)
        with tarfile.open(top_tar_file, mode="a") as tt:
            with tarfile.open(sub_tar_file, mode="r:") as st:
                for m in st.getmembers():
                    # print('adding', m, m.type, m.isfile())
                    if not m.isfile():
                        continue
                    tt.add(m.name)

    # add translation files
    if add_translations:
        with tarfile.open(top_tar_file, mode="a") as tt:
            print("adding translations")
            for file in glob("{}/i18n/*.qm".format(parameters.plugin_path)):
                print("  adding translation: {}".format(
                    os.path.basename(file)))
                # https://stackoverflow.com/a/48462950/1548052
                tt.add(file)

    # compile qrc files
    pyqt5ac.main(ioPaths=[[
        "{}/*.qrc".format(parameters.plugin_path),
        "{}/%%FILENAME%%_rc.py".format(parameters.plugin_path),
    ]])
    for file in glob("{}/*_rc.py".format(parameters.plugin_path)):
        with tarfile.open(top_tar_file, mode="a") as tt:
            print("  adding resource: {}".format(file))
            # https://stackoverflow.com/a/48462950/1548052
            tt.add(file)

    # converting to ZIP
    # why using TAR before? because it provides the prefix and makes things easier
    with zipfile.ZipFile(file=archive_name,
                         mode="w",
                         compression=zipfile.ZIP_DEFLATED) as zf:
        # adding the content of TAR archive
        with tarfile.open(top_tar_file, mode="r:") as tt:
            for m in tt.getmembers():
                if m.isdir():
                    continue
                f = tt.extractfile(m)
                fl = f.read()
                fn = m.name
                zf.writestr(fn, fl)

    print("-------")
    print("files in ZIP archive ({}):".format(archive_name))
    with zipfile.ZipFile(file=archive_name, mode="r") as zf:
        for f in zf.namelist():
            print(f)
    print("-------")

    # checkout to reset changes
    if initial_stash:
        repo.git.reset("--hard", initial_stash)
        repo.git.reset("HEAD^")
    else:
        repo.git.checkout("--", ".")
コード例 #14
0
def main():
    # create the top-level parser
    parser = argparse.ArgumentParser()
    parser.add_argument("-v",
                        "--version",
                        help="print the version and exit",
                        action='store_true')

    subparsers = parser.add_subparsers(title='commands',
                                       description='qgis-plugin-ci command',
                                       dest='command')

    # package
    package_parser = subparsers.add_parser(
        'package', help='creates an archive of the plugin')
    package_parser.add_argument('release_version',
                                help='The version to be released')
    package_parser.add_argument(
        '--transifex-token',
        help=
        'The Transifex API token. If specified translations will be pulled and compiled.'
    )
    package_parser.add_argument(
        '--plugin-repo-url',
        help=
        'If specified, a XML repository file will be created in the current directory, the zip URL will use this parameter.'
    )
    package_parser.add_argument(
        '--allow-uncommitted-changes',
        action='store_true',
        help=
        'If omitted, uncommitted changes are not allowed before packaging. If specified and some changes are '
        'detected, a hard reset on a stash create will be used to revert changes made by qgis-plugin-ci.'
    )
    package_parser.add_argument(
        '--disable-submodule-update',
        action='store_true',
        help=
        'If omitted, a git submodule is updated. If specified, git submodules will not be updated/initialized before packaging.'
    )

    # changelog
    changelog_parser = subparsers.add_parser('changelog',
                                             help='gets the changelog content')
    changelog_parser.add_argument('release_version',
                                  help='The version to be released')

    # release
    release_parser = subparsers.add_parser('release',
                                           help='release the plugin')
    release_parser.add_argument('release_version',
                                help='The version to be released')
    release_parser.add_argument(
        '--transifex-token',
        help=
        'The Transifex API token. If specified translations will be pulled and compiled.'
    )
    release_parser.add_argument(
        '--github-token',
        help=
        'The Github API token. If specified, the archive will be pushed to an already existing release.'
    )
    release_parser.add_argument(
        '--create-plugin-repo',
        action='store_true',
        help=
        'Will create a XML repo as a Github release asset. Github token is required.'
    )
    release_parser.add_argument(
        '--allow-uncommitted-changes',
        action='store_true',
        help=
        'If omitted, uncommitted changes are not allowed before releasing. If specified and some changes are '
        'detected, a hard reset on a stash create will be used to revert changes made by qgis-plugin-ci.'
    )
    release_parser.add_argument(
        '--disable-submodule-update',
        action='store_true',
        help=
        'If omitted, a git submodule is updated. If specified, git submodules will not be updated/initialized before packaging.'
    )
    release_parser.add_argument(
        '--osgeo-username', help='The Osgeo user name to publish the plugin.')
    release_parser.add_argument(
        '--osgeo-password', help='The Osgeo password to publish the plugin.')

    # pull-translation
    pull_tr_parser = subparsers.add_parser(
        'pull-translation', help='pull translations from Transifex')
    pull_tr_parser.add_argument('transifex_token',
                                help='The Transifex API token')
    pull_tr_parser.add_argument('--compile',
                                action='store_true',
                                help='Will compile TS files into QM files')

    # push-translation
    push_tr_parser = subparsers.add_parser(
        'push-translation', help='update strings and push translations')
    push_tr_parser.add_argument('transifex_token',
                                help='The Transifex API token')

    args = parser.parse_args()

    # print the version and exit
    if args.version:
        import pkg_resources
        print('qgis-plugin-ci version: {}'.format(
            pkg_resources.get_distribution('qgis-plugin-ci').version))
        parser.exit()

    # if no command is passed, print the help and exit
    if not args.command:
        parser.print_help()
        parser.exit()

    exit_val = 0

    if os.path.isfile(".qgis-plugin-ci"):
        arg_dict = yaml.safe_load(open(".qgis-plugin-ci"))
    else:
        config = configparser.ConfigParser()
        config.read("setup.cfg")
        if "qgis-plugin-ci" not in config.sections():
            raise ConfigurationNotFound(
                ".qgis-plugin-ci or setup.cfg with a 'qgis-plugin-ci' section have not been found."
            )
        arg_dict = dict(config.items("qgis-plugin-ci"))

    parameters = Parameters(arg_dict)

    # PACKAGE
    if args.command == 'package':
        release(
            parameters,
            release_version=args.release_version,
            transifex_token=args.transifex_token,
            allow_uncommitted_changes=args.allow_uncommitted_changes,
            plugin_repo_url=args.plugin_repo_url,
            disable_submodule_update=args.disable_submodule_update,
        )

    # RELEASE
    elif args.command == 'release':
        release(
            parameters,
            release_version=args.release_version,
            transifex_token=args.transifex_token,
            github_token=args.github_token,
            upload_plugin_repo_github=args.create_plugin_repo,
            osgeo_username=args.osgeo_username,
            osgeo_password=args.osgeo_password,
            allow_uncommitted_changes=args.allow_uncommitted_changes,
            disable_submodule_update=args.disable_submodule_update,
        )

    # CHANGELOG
    elif args.command == 'changelog':
        try:
            c = ChangelogParser(parameters.changelog_regexp)
            content = c.content(args.release_version)
            if content:
                print(content)
        except Exception:
            # Better to be safe
            pass

    # TRANSLATION PULL
    elif args.command == 'pull-translation':
        t = Translation(parameters, args.transifex_token)
        t.pull()
        if args.compile:
            t.compile_strings()

    # TRANSLATION PUSH
    elif args.command == 'push-translation':
        t = Translation(parameters, args.transifex_token)
        t.update_strings()
        t.push()

    return exit_val