コード例 #1
0
ファイル: clean.py プロジェクト: sparr/fac
class CleanCommand(Command):
    """remove old version of mods even if compatible with the game version"""

    name = 'clean'
    arguments = [
        Arg('-y',
            '--yes',
            action='store_true',
            help="automatic yes to confirmation prompt"),
        Arg('-U',
            '--unpacked',
            action='store_false',
            dest='packed',
            default=None,
            help="only remove unpacked mods"),
        Arg(
            '-P',
            '--packed',
            action='store_true',
            dest='packed',
            default=None,
            help="only remove packed mods",
        )
    ]

    def run(self, args):
        mods = self.manager.find_mods()
        names = sorted([(mod.name, mod.version) for mod in mods])
        to_clean = []
        pointer = (None, None)
        for name in names:
            if name[0] != pointer[0]:
                pointer = name
            else:
                if name[1] < pointer[1]:
                    to_clean += [name]
                else:
                    to_clean += [pointer]
                    pointer = name

        to_clean = [
            self.manager.get_mod(mod[0], version=mod[1]) for mod in to_clean
        ]
        if not to_clean:
            print("No old versions of mods to remove.")
            return
        else:
            print("The following mods will be removed:")
            for mod in to_clean:
                print("    %s" % mod.location)

            if not args.yes and prompt("Continue?", "Y/n") != "y":
                return

            for mod in to_clean:
                mod.remove()
コード例 #2
0
ファイル: hold.py プロジェクト: strawberry-code/fac
class UnholdCommand(Command):
    """Unhold mods."""

    name = 'unhold'
    arguments = [
        Arg('mods', help="mods to unhold", nargs='+'),
    ]

    def run(self, args):
        for mod_pattern in args.mods:
            mod_pattern = self.manager.resolve_mod_name(mod_pattern)
            mods = [mod.name for mod in self.manager.find_mods(mod_pattern)]

            if not mods:
                # Special case for mods that have been removed
                # but are still in the hold list
                if mod_pattern in self.config.hold:
                    mods = [mod_pattern]
                else:
                    print("No match found for %s." % mod_pattern)
                    continue

            for mod_name in mods:
                if self.manager.set_mod_held(mod_name, False):
                    print("%s will now be updated automatically." % mod_name)
                else:
                    print("%s is not held." % mod_name)
コード例 #3
0
ファイル: hold.py プロジェクト: strawberry-code/fac
class HoldCommand(Command):
    """Hold mods (show held mods with no argument)."""

    name = 'hold'
    arguments = [
        Arg('mods', help="mods patterns to hold", nargs='*'),
    ]

    def run(self, args):
        for mod_pattern in args.mods:
            mod_pattern = self.manager.resolve_mod_name(mod_pattern)
            mods = self.manager.find_mods(mod_pattern)

            if not mods:
                print("No match found for %s." % mod_pattern)
                continue

            for mod in mods:
                if not mod.held:
                    mod.held = True
                    print("%s will not be updated automatically anymore." %
                          mod.name)
                else:
                    print("%s is already held" % mod.name)

        if not args.mods:
            if self.config.hold:
                print("Mods currently held:")
                for name in self.config.hold:
                    print("    %s" % name)
            else:
                print("No held mods.")
コード例 #4
0
class EnableDisableCommand(Command):
    arguments = [
        Arg('mods', nargs='+', help="mods patterns to affect"),
    ]

    def run(self, args):
        enabled = self.name == 'enable'

        for mod_pattern in args.mods:
            try:
                mod_name = self.manager.resolve_mod_name(mod_pattern)
            except ModNotFoundError as e:
                print("Error: %s" % e)
                continue

            mods = self.manager.find_mods(mod_name)

            if not mods:
                print("No match found for %s" % mod_pattern)
                continue

            for mod in mods:
                if not self.manager.set_mod_enabled(mod.name, enabled):
                    print("%s was already %sd" % (mod.name, self.name))
                else:
                    print("%s is now %sd" % (mod.name, self.name))
コード例 #5
0
ファイル: remove.py プロジェクト: strawberry-code/fac
class RemoveCommand(Command):
    """Remove mods."""

    name = 'remove'
    arguments = [
        Arg('mods', help="mod patterns to remove ('*' for all)", nargs='+'),
        Arg('-y',
            '--yes',
            action='store_true',
            help="automatic yes to confirmation prompt"),
        Arg('-U',
            '--unpacked',
            action='store_false',
            dest='packed',
            default=None,
            help="only remove unpacked mods"),
        Arg(
            '-P',
            '--packed',
            action='store_true',
            dest='packed',
            default=None,
            help="only remove packed mods",
        ),
    ]

    def run(self, args):
        mods = []
        for mod_pattern in args.mods:
            mod_pattern = self.manager.resolve_mod_name(mod_pattern)
            matches = self.manager.find_mods(mod_pattern, packed=args.packed)
            mods.extend(matches)
            if not matches:
                print("No match found for %s." % mod_pattern)

        if mods:
            print("The following mods will be removed:")
            for mod in mods:
                print("    %s" % mod.location)

            if not args.yes and prompt("Continue?", "Y/n") != "y":
                return

            for mod in mods:
                mod.remove()
コード例 #6
0
ファイル: pack.py プロジェクト: strawberry-code/fac
class PackUnpackCommand(Command):
    arguments = [
        Arg('mods', nargs='+', help="mods patterns to affect"),
        Arg('-R',
            '--replace',
            action='store_true',
            help="replace existing file/directory when packing/unpacking"),
        Arg('-K',
            '--keep',
            action='store_true',
            help="keep existing directory/file after packing/unpacking"),
    ]

    def run(self, args):
        pack = self.name == 'pack'

        for mod_pattern in args.mods:
            mod_pattern = self.manager.resolve_mod_name(mod_pattern)
            mods = self.manager.find_mods(mod_pattern, packed=not pack)

            if not mods:
                print("No %sable found for %s." % (self.name, mod_pattern))
                continue

            for mod in mods:
                dup_mod = self.manager.get_mod(mod.name,
                                               mod.version,
                                               packed=pack)

                if dup_mod and not args.replace:
                    print("%s is already %sed. Use -R to replace it." %
                          (mod.name, self.name))
                    continue

                if pack:
                    mod.pack(replace=args.replace, keep=args.keep)
                else:
                    mod.unpack(replace=args.replace, keep=args.keep)

                print("%s is now %sed" % (mod.name, self.name))
コード例 #7
0
class MakeCompatibleCommand(Command):
    """
    Change the supported factorio version of mods.

    This modifies the `factorio_version` field in the mods' info.json file
    to make them compatible with the current game version.

    Packed mods will be unpacked first.
    Unpacked mods will be modified in place.
    """

    name = 'make-compatible'
    arguments = [
        Arg('mods', nargs='+', help="mods patterns to affect"),
    ]

    def run(self, args):
        game_ver = self.config.game_version_major
        for mod_pattern in args.mods:
            mod_pattern = self.manager.resolve_mod_name(mod_pattern)
            mods = [
                mod.unpack(replace=False)
                for mod in self.manager.find_mods(mod_pattern)
                if mod.game_version != game_ver
            ]

            if not mods:
                print("No match for %s." % mod_pattern)
                continue

            for mod in mods:
                print("Game version changed to %s for %s %s." %
                      (game_ver, mod.name, mod.version))

                mod.info.factorio_version = game_ver
                mod.info.save()
コード例 #8
0
class ListCommand(Command):
    """List installed mods and their status."""

    _all_tags = ['disabled', 'unpacked', 'held', 'incompatible']

    name = 'list'

    arguments = [
        Arg('-E',
            '--exclude',
            metavar='TAG',
            nargs='+',
            action='append',
            default=[],
            choices=_all_tags,
            help="exclude mods having any of the specified tags"),
        Arg('-I',
            '--include',
            metavar='TAG',
            nargs='+',
            action='append',
            default=[],
            choices=_all_tags,
            help="only include mods having the specified tags"),
        Arg('-F',
            '--format',
            help="show installed mods using the specified format string."),
    ]
    epilog = """
    Available tags: %s

    An optional format string can be specified with the -F flag.
    You can use this if you want to customize the default output format.

    The syntax of format strings is decribed here:
    https://docs.python.org/3/library/string.html#format-string-syntax

    The provided arguments to the format string are:
        mod  : the mod object (see examples)
        tags : the tags as a space-separated string

    Using the default string ('s') specifier on a JSON list or object will
    output valid JSON.

    Some examples:
        {tags}
        {mod.name}          Mod name
        {mod.version}       Mod version
        {mod.game_version}  Supported game version
        {mod.enabled}       Is the mod enabled? (True/False)
        {mod.packed}        Is the mod packed? (True/False)
        {mod.held}          Is the mod held? (True/False)
        {mod.location}      Mod file/directory path
        {mod.info}          info.json content as JSON
        {mod.info.dependencies}
    """ % (", ".join(_all_tags))

    def run(self, args):
        mods = self.manager.find_mods()
        if not mods:
            print("No installed mods.")
            return

        if not args.format:
            if not args.include and not args.exclude:
                print("Installed mods:")
            else:
                print("Matching mods:")

        found = False
        for mod in sorted(mods, key=lambda m: (not m.enabled, m.name)):
            tags = []
            if not mod.enabled:
                tags.append('disabled')
            if not mod.packed:
                tags.append('unpacked')
            if mod.held:
                tags.append('held')
            if mod.game_version != self.config.game_version_major:
                tags.append('incompatible')

            if any(tag in tags for l in args.exclude for tag in l) or \
               any(tag not in tags for l in args.include for tag in l):
                continue

            tags = ", ".join(tags)
            found = True

            if args.format:
                print(args.format.format(mod=mod, tags=tags))
            else:
                if tags:
                    tags = " (%s)" % tags
                print("    %s %s%s" % (mod.name, mod.version, tags))
        if not found and not args.format:
            print("No matches found.")
コード例 #9
0
ファイル: update.py プロジェクト: sparr/fac
class UpdateCommand(Command):
    """Update installed mods."""

    name = "update"
    arguments = [
        Arg("-s",
            "--show",
            action="store_true",
            help="only show what would be updated"),
        Arg("-y",
            "--yes",
            action="store_true",
            help="automatic yes to confirmation prompt"),
        Arg("-U",
            "--unpacked",
            action="store_true",
            help="allow updating unpacked mods"),
        Arg("-H",
            "--held",
            action="store_true",
            help="allow updating held mods"),
    ]

    def run(self, args):
        installed = self.manager.find_mods()
        updates = []

        if args.ignore_game_ver:
            game_ver = None
        else:
            game_ver = self.config.game_version_major

        self.db.update()

        for local_mod in installed:
            print("Checking: %s" % local_mod.name)

            try:
                release = next(
                    self.manager.get_releases(local_mod.name, game_ver))
            except StopIteration:
                continue

            found_update = False
            local_ver = local_mod.version
            latest_ver = local_ver
            latest_release = None

            for release in remote_mod.releases:
                if not args.ignore_game_ver and \
                        parse_game_version(release) != game_ver:
                    continue

                release_ver = Version(release.version)

                if release_ver > latest_ver:
                    found_update = True
                    latest_ver = release_ver
                    latest_release = release

            update_mod = True
            if found_update:
                print("Found update: %s %s" % (local_mod.name, latest_ver))

                if not args.unpacked and not local_mod.packed:
                    print("%s is unpacked. "
                          "Use -U to update it anyway." % (local_mod.name))
                    update_mod = False

                if not args.held and local_mod.name in self.config.hold:
                    print("%s is held. "
                          "Use -H to update it anyway." % local_mod.name)
                    update_mod = False

                if update_mod:
                    updates.append((local_mod, latest_release))

        if not updates:
            print("No updates were found")
            return

        print("Found %d update%s:" % (
            len(updates),
            "s" if len(updates) != 1 else "",
        ))

        for local_mod, release in updates:
            print("    %s %s -> %s" %
                  (local_mod.name, local_mod.version, release.version))

        if not args.show:
            if not args.yes and prompt("Continue?", "Y/n") != "y":
                return

            for local_mod, release in updates:
                self.manager.install_mod(local_mod.name, release)
コード例 #10
0
class ShowCommand(Command):
    """Show details about specific mods."""

    name = 'show'
    arguments = [
        Arg('mods', help="mods to show", nargs='+'),
        Arg('-F',
            '--format',
            help="show mods using the specified format string."),
        Arg('-S',
            '--sync',
            help="Force database sync",
            action='store_true',
            default=None,
            dest='sync'),
        Arg('--no-sync',
            help="Don't sync database even if it's out of date",
            action='store_false',
            default=None,
            dest='sync'),
    ]

    epilog = """
    FORMAT STRINGS

    An optional format string can be specified with the -F flag.
    You can use this if you want to customize the default output format.

    The syntax of format strings is decribed here:
    https://docs.python.org/3/library/string.html#format-string-syntax

    There is only one argument passed to the format string which is the
    mod object returned by the API.

    Using the default string ('s') specifier on a JSON list or object will
    output valid JSON.

    Some examples:
        {mod}                        JSON as returned by the API
        {mod.name}                   Name of the mod
        {mod.releases[0].version}    Version of first release
        {mod.releases[0].info_json}  info.json of first release

    Note: as a shorthand, you can also use `0` instead of `mod`
    """

    def run(self, args):

        if args.sync is None:
            self.db.maybe_update()
        elif args.sync:
            self.db.update()

        first = True
        for mod in args.mods:
            if first:
                first = False
            else:
                print("-" * 80)

            try:
                mod = self.manager.resolve_mod_name(mod,
                                                    remote=True,
                                                    patterns=False)
                m = self.api.get_mod(mod)
            except ModNotFoundError as ex:
                print("Error: %s" % ex)
                continue

            if args.format:
                print(args.format.format(m, mod=m))
                continue

            print("Name: %s" % m.name)
            print("Author: %s" % m.owner)
            print("Title: %s" % m.title)
            print("Summary: %s" % m.summary)

            # if this ever comes back...
            if 'description' in m:
                print("Description:")

                for line in m.description.splitlines():
                    print("    %s" % line)

            if 'tags' in m and m.tags:
                print("Tags: %s" % ", ".join(tag.name for tag in m.tags))

            if 'homepage' in m and m.homepage:
                print("Homepage: %s" % m.homepage)

            if 'github_path' in m and m.github_path:
                print("GitHub page: https://github.com/%s" % m.github_path)

            if 'license_name' in m:
                print("License: %s" % m.license_name)

            game_versions = sorted(
                set(
                    str(parse_game_version(release))
                    for release in m.releases))

            print("Game versions: %s" % ", ".join(game_versions))

            print("Releases:")
            if not m.releases:
                print("    No releases")
            else:
                for release in m.releases:
                    print("    Version: %-9s Game version: %-9s" % (
                        release.version,
                        parse_game_version(release),
                    ))
コード例 #11
0
class InstallCommand(Command):
    """
    Install (or update) mods.

    This will install mods matching the given requirements using this format:
        name
        name==version
        name>=version
        name<version
        ...

    If the version is not specified, the latest version will be selected.

    Outdated versions will be replaced.
    """
    name = 'install'

    arguments = [
        Arg('requirements',
            nargs='*',
            help="requirements to install "
            '("name", "name>=1.0", "name==1.2", ...)'),
        Arg('-H',
            '--held',
            action='store_true',
            help="allow updating held mods"),
        Arg('-R',
            '--reinstall',
            action='store_true',
            help="allow reinstalling mods"),
        Arg('-D',
            '--downgrade',
            action='store_true',
            help="allow downgrading mods"),
        Arg('-U',
            '--unpack',
            action='store_true',
            default=None,
            help="unpack mods zip files"),
        Arg('-d',
            '--no-deps',
            action='store_true',
            help="do not install any dependencies"),
    ]

    def install(self, args, name, release):
        print("Installing: %s %s..." % (name, release.version))

        self.manager.install_mod(name, release, unpack=args.unpack)

    def run(self, args):
        to_install = []

        for req in args.requirements:
            name, spec = parse_requirement(req)

            try:
                name = self.manager.resolve_mod_name(name, remote=True)
                req = Requirement(name, spec)
                releases = start_iter(
                    self.manager.resolve_remote_requirement(
                        req, ignore_game_ver=args.ignore_game_ver))
            except StopIteration:
                releases = []
            except ModNotFoundError as ex:
                print("Error: %s" % ex)
                continue

            if not args.held and req.name in self.config.hold:
                print("%s is held. "
                      "Use -H to install it anyway." % (req.name))
                continue

            local_mod = self.manager.get_mod(req.name)

            for release in releases:
                if local_mod:
                    local_ver = local_mod.version
                    release_ver = Version(release.version)

                    if not args.reinstall and release_ver == local_ver:
                        print("%s==%s is already installed. "
                              "Use -R to reinstall it." %
                              (local_mod.name, local_ver))
                        break

                    elif not args.downgrade and release_ver < local_ver:
                        print(
                            "%s is already installed in a more recent version."
                            " Use -D to downgrade it." % (local_mod.name))
                        break

                to_install.append((name, release))
                break
            else:
                print("No match found for %s" % (req, ))
                continue

        for name, release in to_install:
            self.install(args, name, release)

        if not args.no_deps:
            self.install_deps(args)

    def install_deps(self, args):
        deps = []

        for mod in self.manager.find_mods():
            try:
                deps += mod.info.dependencies
                self.log.debug("Dependencies needed for %s %s : %s" %
                               (mod.name, mod.version, mod.info.dependencies))
            except AttributeError:
                pass

        deps_to_install = []
        deps_ok = True

        for dep in deps:
            depreq = parse_requirement(dep)

            if depreq.name == 'base':
                continue  # ignore it since it's not like we can install it

            if depreq.name.startswith('?'):
                continue  # ignore optional dependency

            if depreq.name.startswith('(?)'):
                continue  # ignore optional dependency

            if depreq.name.startswith('!'):
                continue  # ignore incompatible dependency

            print("Resolving needed dependency: %s" % dep)
            if self.manager.resolve_local_requirement(
                    depreq, ignore_game_ver=args.ignore_game_ver):
                print("Dependency locally met : %s" % depreq.name)
                continue
            try:
                # FIXME: we only try the first release here
                releases = self.manager.resolve_remote_requirement(
                    depreq, ignore_game_ver=args.ignore_game_ver)
                release = next(releases)
            except ModNotFoundError:
                print("Dependency not found: %s" % depreq.name)
                deps_ok = False
                break
            except StopIteration:
                print("Dependency release not found: %s" % depreq.name)
                print(
                    "Make sure the %s mod is available on mods.factorio.com and compatible with your game version : %s"
                    % (dep, self.config.game_version_major))
                deps_ok = False
                break

            if not release:
                print("Dependency can not be met: %s" % depreq)
                deps_ok = False
                break

            if (depreq.name, release) not in deps_to_install:
                mod = self.manager.get_mod(depreq.name)
                spec = depreq.specifier
                if mod.version in spec:
                    print("Dependency already installed : %s %s" %
                          (mod.name, mod.version))
                    continue
                print("Installing dependency: %s version %s" %
                      (depreq.name, release.version))
                deps_to_install.append((depreq.name, release))

        if not deps_ok:
            return

        if deps_to_install:
            print("Installing missing dependencies...")
            for name, release in deps_to_install:
                self.install(args, name, release)

            # we may have added new sub-dependencies
            self.install_deps(args)
コード例 #12
0
ファイル: search.py プロジェクト: strawberry-code/fac
class SearchCommand(Command):
    """Search the mods database."""

    name = 'search'

    arguments = [
        Arg('query', help="search string", default=(), nargs='*'),

        Arg('-d', help="sort results by most downloaded",
            action='store_const',
            dest='sort',
            const='-downloads'),

        Arg('-a', help="sort results alphabetically",
            action='store_const',
            dest='sort',
            const='title'),

        Arg('-u', help="sort results by most recently updated",
            action='store_const',
            dest='sort',
            const='-updated'),

        Arg('--sort',
            help="comma-separated list of sort fields. "
                 "Prefix a field with - to reverse it",
            dest='sort'),

        Arg('-l', '--limit', type=int,
            help="stop after returning that many results"),

        Arg('-F', '--format',
            help="show results using the specified format string."),

        Arg('-S', '--sync', help="Force database sync",
            action='store_true',
            default=None,
            dest='sync'),

        Arg('--no-sync', help="Don't sync database even if it's out of date",
            action='store_false',
            default=None,
            dest='sync'),
    ]

    epilog = """
    SEARCHING

    Search query strings use the whoosh library.

    Full syntax is described here:
    https://whoosh.readthedocs.io/en/latest/querylang.html

    You can search by a specific field, eg `summary:blueprint` matches all mods
    having the word blueprint in their summary.

    Wildcards can also be used, eg `name:bob*` will match all mods having any
    word starting with bob in their name.

    If you don't specify a field, the search will be done on all default fields
    at the same time: owner, name, title and summary

    The valid fields are:
        name: mod name
        owner: mod owner
        title: mod title
        summary: mod summary (not sortable)
        downloads: download count


    FORMAT STRINGS

    An optional format string can be specified with the -F flag.
    You can use this if you want to customize the default output format.

    The syntax of format strings is decribed here:
    https://docs.python.org/3/library/string.html#format-string-syntax

    There is only one argument passed to the format string which is the
    result object returned by the API.

    Using the default string ('s') specifier on a JSON list or object will
    output valid JSON.

    Some examples:
        {result.name}                   Name of the mod
        {result}                        JSON-repesentation of the result object
        {result.latest_release.version} Latest release version

    Note: as a shorthand, you can also use `0` instead of `result`
    """

    def run(self, args):
        sort = args.sort

        if args.sync is None:
            self.db.maybe_update()
        elif args.sync:
            self.db.update()

        # null queries just list all mods in alphabetical order by default
        if not args.query and not sort:
            sort = 'name'

        hidden = 0

        for result in self.db.search(
                query=' '.join(args.query),
                sortedby=sort,
                limit=args.limit):

            tags = []
            if not match_game_version(result.latest_release,
                                      self.config.game_version_major):
                tags.insert(0, 'incompatible')
                if not args.ignore_game_ver:
                    hidden += 1
                    continue

            if args.format:
                print(args.format.format(result, result=result))
            else:
                print(result.title)
                print("    Name: %s" % result.name)

                if tags:
                    print("    Tags: %s" % (", ".join(tags)))

                print()
                print("\n".join(
                    fill(
                        line,
                        width=get_terminal_size()[0] - 4,
                        tabsize=4,
                        subsequent_indent="    ",
                        initial_indent="    ",
                    )
                    for line in result.summary.splitlines()
                ))
                print()
        if hidden:
            print("Note: %d mods were hidden because they have no "
                  "compatible game versions. Use -i to show them." % hidden,
                  file=sys.stderr)
コード例 #13
0
ファイル: fetch.py プロジェクト: strawberry-code/fac
class FetchCommand(Command):
    """
    Fetch a mod from the mod portal.

    This will fetch the mods matching the given requirements using this format:
        name
        name==version
        name>=version
        name<version
        ...

    If the version is not specified, the latest version will be selected.
    """
    name = 'fetch'

    arguments = [
        Arg('requirements',
            nargs='+',
            help="requirement to fetch "
            '("name", "name>=1.0", "name==1.2", ...)'),
        Arg('-U',
            '--unpack',
            action='store_true',
            default=None,
            help="unpack mods zip files after downloading"),
        Arg('-K',
            '--keep',
            action='store_true',
            help="keep mod zip file after unpacking"),
        Arg('--dest',
            '-d',
            default='.',
            help="destination directory (default: current directory)"),
        Arg('-R',
            '--replace',
            action='store_true',
            help="replace existing file/directory"),
    ]

    def run(self, args):
        for req in args.requirements:
            name, spec = parse_requirement(req)

            try:
                name = self.manager.resolve_mod_name(name, remote=True)
                req = Requirement(name, spec)
                release = next(
                    self.manager.resolve_remote_requirement(
                        req, ignore_game_ver=True))
            except ModNotFoundError as ex:
                print("Error: %s" % ex)
                continue
            except StopIteration:
                print("No match found for %s" % (req, ))
                continue

            file_name = release.file_name
            self.manager.validate_mod_file_name(file_name)
            file_path = os.path.join(args.dest, file_name)

            if os.path.exists(file_path) and not args.replace:
                print("File %s already exists. "
                      "Use -R to replace it." % file_path)
                continue

            if not os.path.isdir(args.dest):
                os.makedirs(args.dest)

            print("Saving to: %s" % file_path)
            mod = self.manager.download_mod(release, file_path)

            if args.unpack:
                mod.unpack(replace=args.replace, keep=args.keep)
コード例 #14
0
ファイル: update.py プロジェクト: strawberry-code/fac
class UpdateCommand(Command):
    """Update installed mods."""

    name = 'update'
    arguments = [
        Arg('-s', '--show', action='store_true',
            help="only show what would be updated"),

        Arg('-y', '--yes', action='store_true',
            help="automatic yes to confirmation prompt"),

        Arg('-U', '--unpacked', action='store_true',
            help="allow updating unpacked mods"),

        Arg('-H', '--held', action='store_true',
            help="allow updating held mods"),
    ]

    def run(self, args):
        installed = self.manager.find_mods()
        updates = []

        if args.ignore_game_ver:
            game_ver = None
        else:
            game_ver = self.config.game_version_major

        self.db.update()

        for local_mod in installed:
            print("Checking: %s" % local_mod.name)

            try:
                release = next(self.manager.get_releases(local_mod.name,
                                                         game_ver))
            except StopIteration:
                continue

            release_ver = Version(release.version)
            local_ver = local_mod.version

            if release_ver > local_ver:
                print("Found update: %s %s" % (
                    local_mod.name, release.version)
                )

                if not args.unpacked and not local_mod.packed:
                    print(
                        "%s is unpacked. "
                        "Use -U to update it anyway." % (
                            local_mod.name
                        )
                    )
                    continue

                if not args.held and local_mod.name in self.config.hold:
                    print("%s is held. "
                          "Use -H to update it anyway." %
                          local_mod.name)
                    break

                updates.append((local_mod, release))
                break

        if not updates:
            print("No updates were found")
            return

        print("Found %d update%s:" % (
            len(updates),
            "s" if len(updates) != 1 else "",
        ))

        for local_mod, release in updates:
            print("    %s %s -> %s" % (
                local_mod.name, local_mod.version, release.version
            ))

        if not args.show:
            if not args.yes and prompt("Continue?", "Y/n") != "y":
                return

            for local_mod, release in updates:
                self.manager.install_mod(local_mod.name, release)