text: #!/usr/bin/env python3
  from .preferences import Preferences
  from .save_type import SaveType
  from .logger import logger
  from .filesystem import FileSystem, get_default_fs
  from .tts import describe_files_by_type, download_file, load_json_file
  from .save import Save
  
  import argparse
  import os.path
  import sys
  import codecs
  import locale
  import _io
  import json
  import zipfile
  import logging
  
  
  class TTS_CLI:
      def __init__(self):
          self.preferences = Preferences()
  
          parser = argparse.ArgumentParser(
              description="Manipulate Tabletop Simulator files")
          parser.add_argument("-d", "--directory",
                              help="Override TTS cache directory")
          parser.add_argument("-l", "--loglevel", help="Set logging level",
                              choices=['debug', 'info', 'warn', 'error'])
          subparsers = parser.add_subparsers(
              dest='parser', title='command', description='Valid commands.')
          subparsers.required = True
  
      # add list command
          parser_list = subparsers.add_parser('list', help="List installed mods.", description='''
      List installed mods.
      If no id is provided, then this will return a list of all installed modules.
      If an id is provided, then this will list the contents of that modules.
      ''')
          group_list = parser_list.add_mutually_exclusive_group()
          group_list.add_argument("-w", "--workshop", action="store_const", metavar='save_type',
                                  dest='save_type', const=SaveType.workshop, help="List workshop files (the default).")
          group_list.add_argument("-s", "--save", action="store_const", metavar='save_type',
                                  dest='save_type', const=SaveType.save, help="List saves.")
          group_list.add_argument("-c", "--chest", action="store_const", metavar='save_type',
                                  dest='save_type', const=SaveType.chest, help="List chest files.")
  
          parser_list.add_argument(
              "id", nargs='?', help="ID of specific mod to list details of.")
          parser_list.set_defaults(func=self.do_list)
  
          # export command
          parser_export = subparsers.add_parser(
              'export', help="Export a mod.", description='Export a mod in a format suitible for later import.')
          group_export = parser_export.add_mutually_exclusive_group()
          group_export.add_argument("-w", "--workshop", action="store_const", dest='save_type',
                                    metavar='save_type', const=SaveType.workshop, help="ID is of workshop file (the default).")
          group_export.add_argument("-s", "--save", action="store_const", dest='save_type',
                                    metavar='save_type', const=SaveType.save, help="ID is of savegame file.")
          group_export.add_argument("-c", "--chest", action="store_const", dest='save_type',
                                    metavar='save_type', const=SaveType.chest, help="ID is of chest file.")
          parser_export.add_argument(
              "id", help="ID of mod/name of savegame to export.")
          parser_export.add_argument(
              "-o", "--output", help="Location/file to export to.")
          parser_export.add_argument(
              "-f", "--force", action="store_true", help="Force creation of export file.")
          parser_export.add_argument("-d", "--download", action="store_true",
                                     help="Attempt to download missing cache files. (EXPERIMENTAL)")
          parser_export.set_defaults(func=self.do_export)
  
          # import command
          parser_import = subparsers.add_parser(
              'import', help="Import a mod.", description="Import an previously exported mod.")
          parser_import.add_argument("file", help="Mod pak file to import.")
          parser_import.set_defaults(func=self.do_import)
  
          # download command
          parser_download = subparsers.add_parser(
              'download', help='Download mod files.', description='Attempt to download any missing files for an installed mod.')
          group_download = parser_download.add_mutually_exclusive_group()
          group_download.add_argument("-w", "--workshop", action="store_const", dest='save_type',
                                      metavar='save_type', const=SaveType.workshop, help="ID is of workshop file.")
          group_download.add_argument("-s", "--save", action="store_const", dest='save_type',
                                      metavar='save_type', const=SaveType.save, help="ID is of savegame file.")
          group_download.add_argument("-c", "--chest", action="store_const", dest='save_type',
                                      metavar='save_type', const=SaveType.chest, help="ID is of chest file.")
          group_download_target = parser_download.add_mutually_exclusive_group(
              required=True)
          group_download_target.add_argument(
              "-a", "--all", action="store_true", help="Download all.")
          group_download_target.add_argument(
              "id", nargs='?', help="ID of mod/name of savegame to download.")
          parser_download.set_defaults(func=self.do_download)
  
          # cache command
          parser_cache = subparsers.add_parser(
              'cache', help='Work with the cache.')
          subparsers_cache = parser_cache.add_subparsers(
              dest='parser_cache', title='cache_command', description='Valid sub-commands.')
          subparsers_cache.required = True
          parser_cache_create = subparsers_cache.add_parser(
              'create', help='(re)create cache directory')
          parser_cache_create.set_defaults(func=self.do_cache_create)
  
          # config command
          parser_config = subparsers.add_parser(
              'config', help='Configure tts manager.')
          subparsers_config = parser_config.add_subparsers(
              dest='parser_config', title='config_command', description='Valid sub-commands.')
          subparsers_config.required = True
          parser_config_list = subparsers_config.add_parser(
              'list', help='List configuration.')
          parser_config_list.set_defaults(func=self.do_config_list)
          parser_config_validate = subparsers_config.add_parser(
              'validate', help='Validate configuration.')
          parser_config_validate.set_defaults(func=self.do_config_validate)
          parser_config_reset = subparsers_config.add_parser(
              'reset', help='Reset configuration.')
          parser_config_reset.set_defaults(func=self.do_config_reset)
          parser_config_set = subparsers_config.add_parser(
              'set', help='Set configuration parameters.')
          parser_config_set.set_defaults(func=self.do_config_set)
          parser_config_set.add_argument(
              "-m", "--mod_location", choices=['documents', 'gamedata'], help="Where mods are stored.")
          parser_config_set.add_argument(
              "-t", "--tts_location", help="TTS Install directory")
  
          args = parser.parse_args()
  
          # set logging
          if args.loglevel:
              logmap = {
                  'debug': logging.DEBUG,
                  'info': logging.INFO,
                  'warn': logging.WARN,
                  'error': logging.ERROR
              }
              logger().setLevel(logmap[args.loglevel])
          else:
              logger().setLevel(logging.WARN)
  
          # load filesystem values
          if args.directory:
              self.filesystem = FileSystem(
                  os.path.abspath(args.directory))
          else:
              self.filesystem = self.preferences.get_filesystem()
  
          if (args.parser == 'list' or args.parser == 'export') and not args.save_type:
              # set default
              args.save_type = SaveType.workshop
  
          if (args.parser == 'config' and args.parser_config == 'set' and not args.mod_location and not args.tts_location):
              parser_config_set.error("At least one of -m or -t is required.")
  
          rc, message = args.func(args)
          if message:
              print(message)
          sys.exit(rc)
  
      def do_config_set(self, args):
          if args.mod_location:
              self.preferences.locationIsUser = args.mod_location == 'documents'
          if args.tts_location:
              self.preferences.TTSLocation = args.mod_location
          self.preferences.save()
          return 0, "Preferences set"
  
      def do_config_reset(self, args):
          self.preferences.reset()
          return 0, "Preferences Reset."
  
      def do_config_list(self, args):
          return 0, self.preferences
  
      def do_config_validate(self, args):
          if self.preferences.validate():
              return 0, "Configuration validated OK."
          else:
              return 1, "Configuration failed to validate."
  
      def do_cache_create(self, args):
          try:
              self.filesystem.create_dirs()
          except OSError as exception:
              return 1, "OS error: {0}".format(exception)
          return 0, "All directories created OK."
  
      def list_by_type(self, save_type):
          result = ""
          for (name, id) in describe_files_by_type(self.filesystem, save_type):
              result += "\n%s (%s)" % (name, id)
          return 0, result
  
      def list_item(self, data, filename, ident):
          if not data:
              # self.list_installed()
              return
          save = Save(savedata=data, ident=ident,
                      filename=filename, filesystem=self.filesystem)
          return 0, save
  
      def do_download(self, args):
          successful = True
          if not args.all:
              if not args.save_type:
                  args.save_type = self.filesystem.get_json_filename_type(
                      args.id)
              if not args.save_type:
                  return 1, "Unable to determine type of id %s" % args.id
              successful = download_file(
                  self.filesystem, args.id, args.save_type)
          else:
              if args.save_type:
                  for ident in self.filesystem.get_filenames_by_type(args.save_type):
                      if not download_file(self.filesystem, ident, args.save_type):
                          successful = False
                          break
              else:
                  for save_type in SaveType:
                      for ident in self.filesystem.get_filenames_by_type(save_type):
                          if not download_file(self.filesystem, ident, save_type):
                              successful = False
                              break
  
          if successful:
              return 0, "All files downloaded."
          else:
              return 1, "Some files failed to download."
  
      def do_list(self, args):
          rc = 0
          result = None
  
          if not args.id:
              rc, result = self.list_by_type(args.save_type)
          else:
              if not args.save_type:
                  args.save_type = self.filesystem.get_json_filename_type(
                      args.id)
              if not args.save_type:
                  return 1, "Unable to determine type of id %s" % args.id
              filename = self.filesystem.get_json_filename_for_type(
                  args.id, args.save_type)
              data = load_json_file(filename)
              rc, result = self.list_item(data, filename, args.id)
          return rc, result
  
      def do_export(self, args):
          filename = None
          if args.output:
              if os.path.isdir(args.output):
                  filename = os.path.join(args.output, args.id+".pak")
              else:
                  filename = args.output
          else:
              filename = args.id+".pak"
  
          data = None
          json_filename = None
          if not args.save_type:
              args.save_type = self.filesystem.get_json_filename_type(args.id)
          if not args.save_type:
              return 1, "Unable to determine type of id %s" % args.id
  
          json_filename = self.filesystem.get_json_filename_for_type(
              args.id, args.save_type)
  
          if not json_filename:
              return 1, "Unable to find filename for id %s (wrong -s/-w/-c specified?)" % args.id
          data = load_json_file(json_filename)
          if not data:
              return 1, "Unable to load data for file %s" % json_filename
  
          save = Save(savedata=data,
                      filename=json_filename,
                      ident=args.id,
                      save_type=args.save_type,
                      filesystem=self.filesystem)
          if not save.isInstalled:
              if not args.download:
                  return 1, "Unable to find all urls required by %s. Rerun with -d to try and download them or open it within TTS.\n%s" % (args.id, save)
              else:
                  logger().info("Downloading missing files...")
                  successful = save.download()
                  if successful:
                      logger().info("Files downloaded successfully.")
                  else:
                      return 1, "Some files failed to download"
          if os.path.isfile(filename) and not args.force:
              return 1, "%s already exists. Please specify another file or use '-f'" % filename
          logger().info("Exporting json file %s to %s" % (args.id, filename))
          save.export(filename)
          # TODO: exception handling
          return 0, "Exported %s to %s" % (args.id, filename)
  
      def do_import(self, args):
          return 0
          # return save.importPak(self.filesystem, args.file)
  
  
  def main():
      # fix windows' poor unicode support
      sys.stdout = _io.TextIOWrapper(sys.stdout.buffer, sys.stdout.encoding,
                                     'replace', sys.stdout.newlines, sys.stdout.line_buffering)
      tts_cli = TTS_CLI()
  
  if __name__ == "__main__":
      # fix windows' poor unicode support
      sys.stdout = _io.TextIOWrapper(sys.stdout.buffer, sys.stdout.encoding,
                                     'replace', sys.stdout.newlines, sys.stdout.line_buffering)
      tts_cli = TTS_CLI()
  
  
  