files: #!/usr/bin/env python
  #
  # Copyright (C) 2018 Linus Jahn <lnj@kaidan.im>
  # Copyright (C) 2019-2021 Hiroshi Miura <miurahr@linux.com>
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  import posixpath
  from dataclasses import dataclass, field
  from logging import getLogger
  from typing import Dict, Iterable, List, Optional, Tuple
  
  from defusedxml import ElementTree
  
  from aqt.exceptions import ArchiveDownloadError, ArchiveListError, NoPackageFound
  from aqt.helper import Settings, get_hash, getUrl, ssplit
  from aqt.metadata import QtRepoProperty, Version
  
  
  @dataclass
  class TargetConfig:
      version: str
      target: str
      arch: str
      os_name: str
  
      def __post_init__(self):
          self.version = str(self.version)
  
  
  @dataclass
  class QtPackage:
      name: str
      base_url: str
      archive_path: str
      archive: str
      package_desc: str
      pkg_update_name: str
      version: Optional[Version] = field(default=None)
  
      def __repr__(self):
          v_info = f", version={self.version}" if self.version else ""
          return f"QtPackage(name={self.name}, archive={self.archive}{v_info})"
  
      def __str__(self):
          v_info = f", version={self.version}" if self.version else ""
          return (
              f"QtPackage(name={self.name}, url={self.archive_path}, "
              f"archive={self.archive}, desc={self.package_desc}"
              f"{v_info})"
          )
  
  
  class ModuleToPackage:
      """
      Holds a mapping of module names to a list of Updates.xml PackageUpdate names.
      For example, we could have the following:
      {"qtcharts": ["qt.qt6.620.addons.qtcharts.arch", qt.qt6.620.qtcharts.arch", qt.620.addons.qtcharts.arch",])
      It also contains a reverse mapping of PackageUpdate names to module names, so that
      lookup of a package name and removal of a module name can be done in constant time.
      Without this reverse mapping, QtArchives._parse_update_xml would run at least one
      linear search on the forward mapping for each module installed.
  
      The list of PackageUpdate names consists of all the possible names for the PackageUpdate.
      The naming conventions for each PackageUpdate are not predictable, so we need to maintain
      a list of possibilities. While reading Updates.xml, if we encounter any one of the package
      names on this list, we can use it to install the package "qtcharts".
  
      Once we have installed the package, we need to remove the package "qtcharts" from this
      mapping, so we can keep track of what still needs to be installed.
      """
  
      def __init__(self, initial_map: Dict[str, List[str]]):
          self._modules_to_packages: Dict[str, List[str]] = initial_map
          self._packages_to_modules: Dict[str, str] = {
              value: key for key, list_of_values in initial_map.items() for value in list_of_values
          }
  
      def add(self, module_name: str, package_names: List[str]):
          self._modules_to_packages[module_name] = self._modules_to_packages.get(module_name, []) + package_names
          for package_name in package_names:
              assert package_name not in
  
  [... the rest is omitted ...]
files: aqt/archives.py
files: #!/usr/bin/env python
  #
  # Copyright (C) 2018 Linus Jahn <lnj@kaidan.im>
  # Copyright (C) 2019-2021 Hiroshi Miura <miurahr@linux.com>
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  import binascii
  import posixpath
  from dataclasses import dataclass, field
  from logging import getLogger
  from typing import Dict, Iterable, List, Optional, Tuple
  
  from defusedxml import ElementTree
  
  from aqt.exceptions import ArchiveDownloadError, ArchiveListError, ChecksumDownloadFailure, NoPackageFound
  from aqt.helper import Settings, get_hash, getUrl, ssplit
  from aqt.metadata import QtRepoProperty, Version
  
  
  @dataclass
  class TargetConfig:
      version: str
      target: str
      arch: str
      os_name: str
  
      def __post_init__(self):
          self.version = str(self.version)
  
  
  @dataclass
  class QtPackage:
      name: str
      base_url: str
      archive_path: str
      archive: str
      package_desc: str
      pkg_update_name: str
      version: Optional[Version] = field(default=None)
  
      def __repr__(self):
          v_info = f", version={self.version}" if self.version else ""
          return f"QtPackage(name={self.name}, archive={self.archive}{v_info})"
  
      def __str__(self):
          v_info = f", version={self.version}" if self.version else ""
          return (
              f"QtPackage(name={self.name}, url={self.archive_path}, "
              f"archive={self.archive}, desc={self.package_desc}"
              f"{v_info})"
          )
  
  
  class ModuleToPackage:
      """
      Holds a mapping of module names to a list of Updates.xml PackageUpdate names.
      For example, we could have the following:
      {"qtcharts": ["qt.qt6.620.addons.qtcharts.arch", qt.qt6.620.qtcharts.arch", qt.620.addons.qtcharts.arch",])
      It also contains a reverse mapping of PackageUpdate names to module names, so that
      lookup of a package name and removal of a module name can be done in constant time.
      Without this reverse mapping, QtArchives._parse_update_xml would run at least one
      linear search on the forward mapping for each module installed.
  
      The list of PackageUpdate names consists of all the possible names for the PackageUpdate.
      The naming conventions for each PackageUpdate are not predictable, so we need to maintain
      a list of possibilities. While reading Updates.xml, if we encounter any one of the package
      names on this list, we can use it to install the package "qtcharts".
  
      Once we have installed the package, we need to remove the package "qtcharts" from this
      mapping, so we can keep track of what still needs to be installed.
      """
  
      def __init__(self, initial_map: Dict[str, List[str]]):
          self._modules_to_packages: Dict[str, List[str]] = initial_map
          self._packages_to_modules: Dict[str, str] = {
              value: key for key, list_of_values in initial_map.items() for value in list_of_values
          }
  
      def add(self, module_name: str, package_names: List[str]):
          self._modules_to_packages[module_name] = self._modules_to_packages.get(module_name, []) + package_names
          for package_name
  
  [... the rest is omitted ...]
files: aqt/archives.py
files: #!/usr/bin/env python
  #
  # Copyright (C) 2019-2021 Hiroshi Miura <miurahr@linux.com>
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  import binascii
  import configparser
  import hashlib
  import json
  import logging.config
  import os
  import posixpath
  import secrets
  import sys
  from logging import getLogger
  from logging.handlers import QueueListener
  from pathlib import Path
  from typing import Callable, Dict, Generator, List, Optional, Tuple
  from urllib.parse import urlparse
  from xml.etree.ElementTree import Element
  
  import requests
  import requests.adapters
  from defusedxml import ElementTree
  
  from aqt.exceptions import (
      ArchiveChecksumError,
      ArchiveConnectionError,
      ArchiveDownloadError,
      ArchiveListError,
      ChecksumDownloadFailure,
  )
  
  
  def _get_meta(url: str):
      return requests.get(url + ".meta4")
  
  
  def _check_content_type(ct: str) -> bool:
      candidate = ["application/metalink4+xml", "text/plain"]
      return any(ct.startswith(t) for t in candidate)
  
  
  def getUrl(url: str, timeout, expected_hash: Optional[bytes] = None) -> str:
      """
      Gets a file from `url` via HTTP GET.
  
      No caller should call this function without providing an expected_hash, unless
      the caller is `get_hash`, which cannot know what the expected hash should be.
      """
      logger = getLogger("aqt.helper")
      with requests.sessions.Session() as session:
          retries = requests.adapters.Retry(
              total=Settings.max_retries_on_connection_error, backoff_factor=Settings.backoff_factor
          )
          adapter = requests.adapters.HTTPAdapter(max_retries=retries)
          session.mount("http://", adapter)
          session.mount("https://", adapter)
          try:
              r = session.get(url, allow_redirects=False, timeout=timeout)
              num_redirects = 0
              while 300 < r.status_code < 309 and num_redirects < 10:
                  num_redirects += 1
                  logger.debug("Asked to redirect({}) to: {}".format(r.status_code, r.headers["Location"]))
                  newurl = altlink(r.url, r.headers["Location"])
                  logger.info("Redirected: {}".format(urlparse(newurl).hostname))
                  r = session.get(newurl, stream=True, timeout=timeout)
          except (
              ConnectionResetError,
              requests.exceptions.ConnectionError,
              requests.exceptions.Timeout,
          ) as e:
              raise ArchiveConnectionError(f"Failure to connect to {url}: {type(e).__name__}") from e
          else:
              if r.status_code != 200:
                  msg = f"Failed to retrieve file at {url}\nServer response code: {r.status_code}, reason: {r.reason}"
                  raise ArchiveDownloadError(msg)
          result = r.text
          filename = url.split("/")[-1]
          actual_hash = hashlib.sha256(bytes(result, "utf-8")).digest()
          if expected_hash is not None and expected_hash != actual_hash:
              raise ArchiveChecksumError(
                  f"Downloaded file {filename} is corrupted! Detect checksum error.\n"
                  f"Expect {expected_hash.hex()}: {url}\n"
                  f"Actual {actual_hash.hex()}: {filename}"
              )
      return result
  
  
  def
  
  [... the rest is omitted ...]
files: aqt/helper.py
files: #!/usr/bin/env python
  #
  # Copyright (C) 2019-2021 Hiroshi Miura <miurahr@linux.com>
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  import configparser
  import hashlib
  import json
  import logging.config
  import os
  import posixpath
  import secrets
  import sys
  from logging import getLogger
  from logging.handlers import QueueListener
  from pathlib import Path
  from typing import Callable, Dict, Generator, List, Optional, Tuple
  from urllib.parse import urlparse
  from xml.etree.ElementTree import Element
  
  import requests
  import requests.adapters
  from defusedxml import ElementTree
  
  from aqt.exceptions import (
      ArchiveChecksumError,
      ArchiveConnectionError,
      ArchiveDownloadError,
      ArchiveListError,
      ChecksumDownloadFailure,
  )
  
  
  def _get_meta(url: str):
      return requests.get(url + ".meta4")
  
  
  def _check_content_type(ct: str) -> bool:
      candidate = ["application/metalink4+xml", "text/plain"]
      return any(ct.startswith(t) for t in candidate)
  
  
  def getUrl(url: str, timeout, expected_hash: Optional[bytes] = None) -> str:
      """
      Gets a file from `url` via HTTP GET.
  
      No caller should call this function without providing an expected_hash, unless
      the caller is `get_hash`, which cannot know what the expected hash should be.
      """
      logger = getLogger("aqt.helper")
      with requests.sessions.Session() as session:
          retries = requests.adapters.Retry(
              total=Settings.max_retries_on_connection_error, backoff_factor=Settings.backoff_factor
          )
          adapter = requests.adapters.HTTPAdapter(max_retries=retries)
          session.mount("http://", adapter)
          session.mount("https://", adapter)
          try:
              r = session.get(url, allow_redirects=False, timeout=timeout)
              num_redirects = 0
              while 300 < r.status_code < 309 and num_redirects < 10:
                  num_redirects += 1
                  logger.debug("Asked to redirect({}) to: {}".format(r.status_code, r.headers["Location"]))
                  newurl = altlink(r.url, r.headers["Location"])
                  logger.info("Redirected: {}".format(urlparse(newurl).hostname))
                  r = session.get(newurl, stream=True, timeout=timeout)
          except (
              ConnectionResetError,
              requests.exceptions.ConnectionError,
              requests.exceptions.Timeout,
          ) as e:
              raise ArchiveConnectionError(f"Failure to connect to {url}: {type(e).__name__}") from e
          else:
              if r.status_code != 200:
                  msg = f"Failed to retrieve file at {url}\nServer response code: {r.status_code}, reason: {r.reason}"
                  raise ArchiveDownloadError(msg)
          result = r.text
          filename = url.split("/")[-1]
          actual_hash = hashlib.sha256(bytes(result, "utf-8")).digest()
          if expected_hash is not None and expected_hash != actual_hash:
              raise ArchiveChecksumError(
                  f"Downloaded file {filename} is corrupted! Detect checksum error.\n"
                  f"Expect {expected_hash.hex()}: {url}\n"
                  f"Actual {actual_hash.hex()}: {filename}"
              )
      return result
  
  
  def downloadBinaryFile(url:
  
  [... the rest is omitted ...]
files: aqt/helper.py
files: #!/usr/bin/env python3
  #
  # Copyright (C) 2018 Linus Jahn <lnj@kaidan.im>
  # Copyright (C) 2019-2021 Hiroshi Miura <miurahr@linux.com>
  # Copyright (C) 2020, Aurélien Gâteau
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  import argparse
  import gc
  import multiprocessing
  import os
  import platform
  import posixpath
  import signal
  import subprocess
  import sys
  import time
  from logging import getLogger
  from logging.handlers import QueueHandler
  from pathlib import Path
  from tempfile import TemporaryDirectory
  from typing import List, Optional
  
  import aqt
  from aqt.archives import QtArchives, QtPackage, SrcDocExamplesArchives, ToolArchives
  from aqt.exceptions import (
      AqtException,
      ArchiveChecksumError,
      ArchiveDownloadError,
      ArchiveExtractionError,
      ArchiveListError,
      CliInputError,
      CliKeyboardInterrupt,
      OutOfMemory,
  )
  from aqt.helper import (
      MyQueueListener,
      Settings,
      downloadBinaryFile,
      get_hash,
      retry_on_bad_connection,
      retry_on_errors,
      setup_logging,
  )
  from aqt.metadata import ArchiveId, MetadataFactory, QtRepoProperty, SimpleSpec, Version, show_list, suggested_follow_up
  from aqt.updater import Updater
  
  try:
      import py7zr
  
      EXT7Z = False
  except ImportError:
      EXT7Z = True
  
  
  class Cli:
      """CLI main class to parse command line argument and launch proper functions."""
  
      __slot__ = ["parser", "combinations", "logger"]
  
      UNHANDLED_EXCEPTION_CODE = 254
  
      def __init__(self):
          parser = argparse.ArgumentParser(
              prog="aqt",
              description="Another unofficial Qt Installer.\naqt helps you install Qt SDK, tools, examples and others\n",
              formatter_class=argparse.RawTextHelpFormatter,
              add_help=True,
          )
          parser.add_argument(
              "-c",
              "--config",
              type=argparse.FileType("r"),
              help="Configuration ini file.",
          )
          subparsers = parser.add_subparsers(
              title="subcommands",
              description="aqt accepts several subcommands:\n"
              "install-* subcommands are commands that install components\n"
              "list-* subcommands are commands that show available components\n\n"
              "commands {install|tool|src|examples|doc} are deprecated and marked for removal\n",
              help="Please refer to each help message by using '--help' with each subcommand",
          )
          self._make_all_parsers(subparsers)
          parser.set_defaults(func=self.show_help)
          self.parser = parser
  
      def run(self, arg=None) -> int:
          args = self.parser.parse_args(arg)
          self._setup_settings(args)
          try:
              args.func(args)
              return 0
          except AqtException as e:
              self.logger.error(format(e), exc_info=Settings.print_stacktrace_on_error)
              if e.should_show_help:
                  self.show_help()
              return 1
          except Exception as e:
              # If we didn't account for it, and wrap it in an AqtException, it's a bug.
              self.logger.exception(e)  # Print stack trace
              self.logger.error(
                 
  
  [... the rest is omitted ...]
files: aqt/installer.py
files: #!/usr/bin/env python3
  #
  # Copyright (C) 2018 Linus Jahn <lnj@kaidan.im>
  # Copyright (C) 2019-2021 Hiroshi Miura <miurahr@linux.com>
  # Copyright (C) 2020, Aurélien Gâteau
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  import argparse
  import binascii
  import gc
  import multiprocessing
  import os
  import platform
  import posixpath
  import signal
  import subprocess
  import sys
  import time
  from logging import getLogger
  from logging.handlers import QueueHandler
  from pathlib import Path
  from tempfile import TemporaryDirectory
  from typing import List, Optional
  
  import aqt
  from aqt.archives import QtArchives, QtPackage, SrcDocExamplesArchives, ToolArchives
  from aqt.exceptions import (
      AqtException,
      ArchiveChecksumError,
      ArchiveDownloadError,
      ArchiveExtractionError,
      ArchiveListError,
      CliInputError,
      CliKeyboardInterrupt,
      OutOfMemory,
  )
  from aqt.helper import (
      MyQueueListener,
      Settings,
      downloadBinaryFile,
      get_hash,
      retry_on_bad_connection,
      retry_on_errors,
      setup_logging,
  )
  from aqt.metadata import ArchiveId, MetadataFactory, QtRepoProperty, SimpleSpec, Version, show_list, suggested_follow_up
  from aqt.updater import Updater
  
  try:
      import py7zr
  
      EXT7Z = False
  except ImportError:
      EXT7Z = True
  
  
  class Cli:
      """CLI main class to parse command line argument and launch proper functions."""
  
      __slot__ = ["parser", "combinations", "logger"]
  
      UNHANDLED_EXCEPTION_CODE = 254
  
      def __init__(self):
          parser = argparse.ArgumentParser(
              prog="aqt",
              description="Another unofficial Qt Installer.\naqt helps you install Qt SDK, tools, examples and others\n",
              formatter_class=argparse.RawTextHelpFormatter,
              add_help=True,
          )
          parser.add_argument(
              "-c",
              "--config",
              type=argparse.FileType("r"),
              help="Configuration ini file.",
          )
          subparsers = parser.add_subparsers(
              title="subcommands",
              description="aqt accepts several subcommands:\n"
              "install-* subcommands are commands that install components\n"
              "list-* subcommands are commands that show available components\n\n"
              "commands {install|tool|src|examples|doc} are deprecated and marked for removal\n",
              help="Please refer to each help message by using '--help' with each subcommand",
          )
          self._make_all_parsers(subparsers)
          parser.set_defaults(func=self.show_help)
          self.parser = parser
  
      def run(self, arg=None) -> int:
          args = self.parser.parse_args(arg)
          self._setup_settings(args)
          try:
              args.func(args)
              return 0
          except AqtException as e:
              self.logger.error(format(e), exc_info=Settings.print_stacktrace_on_error)
              if e.should_show_help:
                  self.show_help()
              return 1
          except Exception as e:
              # If we didn't account for it, and wrap it in an AqtException, it's a bug.
              self.logger.exception(e)  # Print stack trace
              self
  
  [... the rest is omitted ...]
files: aqt/installer.py
files: #!/usr/bin/env python
  #
  # Copyright (C) 2021 David Dalcino
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  import itertools
  import operator
  import posixpath
  import re
  import secrets as random
  import shutil
  from logging import getLogger
  from typing import Callable, Dict, Generator, Iterable, Iterator, List, Optional, Tuple, Union
  from xml.etree.ElementTree import Element
  
  import bs4
  from semantic_version import SimpleSpec as SemanticSimpleSpec
  from semantic_version import Version as SemanticVersion
  from texttable import Texttable
  
  from aqt.exceptions import ArchiveConnectionError, ArchiveDownloadError, ArchiveListError, CliInputError, EmptyMetadata
  from aqt.helper import Settings, get_hash, getUrl, xml_to_modules
  
  
  class SimpleSpec(SemanticSimpleSpec):
      pass
  
      @staticmethod
      def usage() -> str:
          return (
              "See documentation at: "
              "https://python-semanticversion.readthedocs.io/en/latest/reference.html#semantic_version.SimpleSpec\n"
              "Examples:\n"
              '* "*": matches everything\n'
              '* "5": matches every version with major=5\n'
              '* "5.6": matches every version beginning with 5.6\n'
              '* "5.*.3": matches versions with major=5 and patch=3'
          )
  
  
  class Version(SemanticVersion):
      """Override semantic_version.Version class
      to accept Qt versions and tools versions
      If the version ends in `-preview`, the version is treated as a preview release.
      """
  
      def __init__(
          self,
          version_string=None,
          major=None,
          minor=None,
          patch=None,
          prerelease=None,
          build=None,
          partial=False,
      ):
          if version_string is None:
              super(Version, self).__init__(
                  version_string=None,
                  major=major,
                  minor=minor,
                  patch=patch,
                  prerelease=prerelease,
                  build=build,
                  partial=partial,
              )
              return
          # test qt versions
          match = re.match(r"^(\d+)\.(\d+)(\.(\d+)|-preview)$", version_string)
          if not match:
              # bad input
              raise ValueError("Invalid version string: '{}'".format(version_string))
          major, minor, end, patch = match.groups()
          is_preview = end == "-preview"
          super(Version, self).__init__(
              major=int(major),
              minor=int(minor),
              patch=int(patch) if patch else 0,
              prerelease=("preview",) if is_preview else None,
          )
  
      def __str__(self):
          if self.prerelease:
              return "{}.{}-preview".format(self.major, self.minor)
          return super(Version, self).__str__()
  
      @classmethod
      def permissive(cls, version_string: str):
          """Converts a version string with dots (5.X.Y, etc) into a semantic version.
          If the version omits either the patch or minor versions, they will be filled in with zeros,
          and the remaining version string becomes part of the prerelease component.
          If the version cannot be converted to a Version, a ValueError is raised.
  
          This is intended to be used on Version tags in an Updates.xml file.
  
          '1.33.1-202102101246' => Version('1.33.1-202102101246')
         
  
  [... the rest is omitted ...]
files: aqt/metadata.py
files: #!/usr/bin/env python
  #
  # Copyright (C) 2021 David Dalcino
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  import binascii
  import itertools
  import operator
  import posixpath
  import re
  import secrets as random
  import shutil
  from logging import getLogger
  from typing import Callable, Dict, Generator, Iterable, Iterator, List, Optional, Tuple, Union
  from xml.etree.ElementTree import Element
  
  import bs4
  from semantic_version import SimpleSpec as SemanticSimpleSpec
  from semantic_version import Version as SemanticVersion
  from texttable import Texttable
  
  from aqt.exceptions import ArchiveConnectionError, ArchiveDownloadError, ArchiveListError, CliInputError, EmptyMetadata
  from aqt.helper import Settings, get_hash, getUrl, xml_to_modules
  
  
  class SimpleSpec(SemanticSimpleSpec):
      pass
  
      @staticmethod
      def usage() -> str:
          return (
              "See documentation at: "
              "https://python-semanticversion.readthedocs.io/en/latest/reference.html#semantic_version.SimpleSpec\n"
              "Examples:\n"
              '* "*": matches everything\n'
              '* "5": matches every version with major=5\n'
              '* "5.6": matches every version beginning with 5.6\n'
              '* "5.*.3": matches versions with major=5 and patch=3'
          )
  
  
  class Version(SemanticVersion):
      """Override semantic_version.Version class
      to accept Qt versions and tools versions
      If the version ends in `-preview`, the version is treated as a preview release.
      """
  
      def __init__(
          self,
          version_string=None,
          major=None,
          minor=None,
          patch=None,
          prerelease=None,
          build=None,
          partial=False,
      ):
          if version_string is None:
              super(Version, self).__init__(
                  version_string=None,
                  major=major,
                  minor=minor,
                  patch=patch,
                  prerelease=prerelease,
                  build=build,
                  partial=partial,
              )
              return
          # test qt versions
          match = re.match(r"^(\d+)\.(\d+)(\.(\d+)|-preview)$", version_string)
          if not match:
              # bad input
              raise ValueError("Invalid version string: '{}'".format(version_string))
          major, minor, end, patch = match.groups()
          is_preview = end == "-preview"
          super(Version, self).__init__(
              major=int(major),
              minor=int(minor),
              patch=int(patch) if patch else 0,
              prerelease=("preview",) if is_preview else None,
          )
  
      def __str__(self):
          if self.prerelease:
              return "{}.{}-preview".format(self.major, self.minor)
          return super(Version, self).__str__()
  
      @classmethod
      def permissive(cls, version_string: str):
          """Converts a version string with dots (5.X.Y, etc) into a semantic version.
          If the version omits either the patch or minor versions, they will be filled in with zeros,
          and the remaining version string becomes part of the prerelease component.
          If the version cannot be converted to a Version, a ValueError is raised.
  
          This is intended to be used on Version tags in an Updates.xml file.
  
          '1.33.1-202102101246' => Version('1.33.1-202
  
  [... the rest is omitted ...]
files: aqt/metadata.py