예제 #1
0
    def file_is_valid(cls, view, file_path=None):
        """Returns a boolean whether ``file_path`` is a valid file for
        this loader.
        """
        file_path = file_path or view and view.file_name()
        if not file_path:
            return None

        return (cls.get_ext_appendix(file_path) is not None
                or file_path_tuple(file_path).ext == '.' + cls.ext
                or (cls.scope is not None and view
                    and base_scope(view) == cls.scope))
예제 #2
0
    def file_is_valid(cls, view, file_path=None):
        """Returns a boolean whether ``file_path`` is a valid file for
        this loader.
        """
        file_path = file_path or view and view.file_name()
        if not file_path:
            return None

        return (cls.get_ext_appendix(file_path) is not None
                or file_path_tuple(file_path).ext == '.' + cls.ext
                or (cls.scope is not None and view
                    and base_scope(view) == cls.scope))
예제 #3
0
    def run(self, edit=None, source_format=None, target_format=None, ext=None,
            open_new_file=False, rearrange_yaml_syntax_def=False, _output=None, *args, **kwargs):
        """Available parameters:

            edit (sublime.Edit) = None
                The edit parameter from TextCommand. Unused.

            source_format (str) = None
                The source format. Any of "yaml", "plist" or "json".
                If `None`, attempt to automatically detect the format by extension, used syntax
                highlight or (with plist) the actual contents.

            target_format (str) = None
                The target format. Any of "yaml", "plist" or "json".
                If `None`, attempt to find an option set in the file to parse.
                If unable to find an option, ask the user directly with all available format options.

            ext (str) = None
                The extension of the file to convert to, without leading dot. If `None`, the extension
                will be automatically determined by a special algorythm using "appendixes".

                Here are a few examples:
                    ".YAML-ppplist" yaml  -> plist ".ppplist"
                    ".json"         json  -> yaml  ".yaml"
                    ".tmplist"      plist -> json  ".JSON-tmplist"
                    ".yaml"         json  -> plist ".JSON-yaml" (yes, doesn't make much sense)

            open_new_file (bool) = False
                Open the (newly) created file in a new buffer.

            rearrange_yaml_syntax_def (bool) = False
                Interesting for language definitions, will automatically run
                "rearrange_yaml_syntax_def" command on it, if the target format is "yaml".
                Overrides "open_new_file" parameter.

            _output (OutputPanel) = None
                For internal use only.

            *args
                Forwarded to pretty much every relevant call but does not have any effect.
                You can't pass *args in commands anyway.

            **kwargs
                Will be forwarded to both the loading function and the dumping function, after stripping
                unsopported entries. Only do this if you know what you're doing.

                Functions in question:
                    yaml.dump
                    json.dump
                    plist.writePlist (does not support any parameters)

                A more detailed description of each supported parameter for the respective dumper can be
                found in `fileconv/dumpers.py`.
        """
        # TODO: Ditch *args, can't be passed in commands anyway

        # Check the environment (view, args, ...)

        if self.view.is_dirty():
            # Save the file so that source and target file on the drive don't differ
            self.view.run_command("save")
            if self.view.is_dirty():
                return sublime.error_message("The file could not be saved correctly. "
                                             "The build was aborted")

        file_path = self.view.file_name()
        if not file_path:
            return self.status("File does not exist.", file_path)

        if source_format and target_format == source_format:
            return self.status("Target and source file format are identical. (%s)" % target_format)

        if source_format and source_format not in loaders.get:
            return self.status("%s for '%s' not supported/implemented." % ("Loader", source_format))

        if target_format and target_format not in dumpers.get:
            return self.status("%s for '%s' not supported/implemented." % ("Dumper", target_format))

        # Now the actual "building" starts (collecting remaining parameters)
        with OutputPanel(self.window, "package_dev") as output:
            output.show()

            # Auto-detect the file type if it's not specified
            if not source_format:
                output.write("Input type not specified, auto-detecting...")
                for Loader in loaders.get.values():
                    if Loader.file_is_valid(self.view):
                        source_format = Loader.ext
                        output.write_line(' %s\n' % Loader.name)
                        break

                if not source_format:
                    return output.write_line("\nUnable to detect file type.")
                elif target_format == source_format:
                    return output.write_line("File already is %s." % loaders.get[source_format].name)

            # Load inline options
            Loader = loaders.get[source_format]
            opts = Loader.load_options(self.view)

            # Function to determine the new file extension depending on the target format
            def get_new_ext(target_format):
                if ext:
                    return '.' + ext
                if opts and 'ext' in opts:
                    return '.' + opts['ext']
                else:
                    new_ext, prepend_target_format = Loader.get_new_file_ext(self.view)
                    if prepend_target_format:
                        new_ext = ".%s-%s" % (target_format.upper(), new_ext[1:])

                return (new_ext or '.' + target_format)

            path_tuple = file_path_tuple(file_path)  # This is the latest point possible

            if not target_format:
                output.write("No target format specified, searching in file...")

                # No information about a target format; ask for it
                if not opts or 'target_format' not in opts:
                    output.write(" Could not detect target format.\n"
                                 "Please select or define a target format...")

                    # Show overlay with all dumping options except for the current type
                    # Save stripped-down `items` for later
                    options, items = [], []
                    for itm in self.target_list:
                        # To not clash with function-local "target_format"
                        target_format_ = itm['kwargs']['target_format']
                        if target_format_ != source_format:
                            options.append(["Convert to: %s" % itm['name'],
                                            path_tuple.base_name + get_new_ext(target_format_)])
                            items.append(itm)

                    def on_select(index):
                        if index < 0 or index >= len(items):
                            # canceled or other magic
                            output.write_line("\n\nBuild canceled.")
                            return

                        target = items[index]
                        output.write_line(' %s\n' % target['name'])

                        kwargs.update(target['kwargs'])
                        kwargs.update(dict(source_format=source_format, _output=output))
                        self.run(*args, **kwargs)

                    # Forward all params to the new command call
                    self.window.show_quick_panel(options, on_select)
                    return

                target_format = opts['target_format']
                # Validate the shit again, but this time print to output panel
                if source_format is not None and target_format == source_format:
                    return output.write_line("\nTarget and source file format are identical. (%s)" % target_format)

                if target_format not in dumpers.get:
                    return output.write_line("\n%s for '%s' not supported/implemented." % ("Dumper", target_format))

                output.write_line(' %s\n' % dumpers.get[target_format].name)

            start_time = time.time()

            # Okay, THIS is where the building really starts
            # Note: loader or dumper errors are not caught in order to receive a nice traceback in the console
            loader_ = Loader(self.window, self.view, output=output)
            try:
                data = loader_.load(*args, **kwargs)
            except:
                output.write_line("Unexpected error occured while parsing, "
                                  "please see the console for details.")
                raise
            if not data:
                return

            # Determine new file name
            new_file_path = path_tuple.no_ext + get_new_ext(target_format)
            new_dir = os.path.dirname(new_file_path)
            if not os.path.exists(new_dir):
                try:
                    os.makedirs(new_dir)
                except OSError:
                    output.write_line("Could not create folder '%s'" % new_dir)
                    return

            # Now dump to new file
            dumper = dumpers.get[target_format](self.window, self.view, new_file_path, output=output)
            try:
                success = dumper.dump(data, *args, **kwargs)
            except:
                output.write_line("Unexpected error occured while dumping, "
                                  "please see the console for details.")
                raise
            self.status("File conversion %ssuccessful. (%s -> %s)"
                        % ("" if success else "un", source_format, target_format))
            if not success:
                return

            # Finish
            output.write("[Finished in %.3fs]" % (time.time() - start_time))

            if open_new_file or rearrange_yaml_syntax_def:
                new_view = self.window.open_file(new_file_path)

                if rearrange_yaml_syntax_def:
                    # For some reason, ST would still indicate the new buffer having "usaved changes"
                    # even though there aren't any (calling "save" command here).
                    new_view.run_command("rearrange_yaml_syntax_def", {"save": True})
예제 #4
0
class ConvertFileCommand(WindowAndTextCommand):
    """Convert a file (view's buffer) of type ``source_format`` to type
    ``target_format``.

        Supports the following parsers/loaders:
            'json'
            'plist'
            'yaml'

        Supports the following writers/dumpers:
            'json'
            'plist'
            'yaml'

        The file extesion is determined by a special algorythm using
        "appendixes". Here are a few examples:
            ".YAML-ppplist" yaml  -> plist ".ppplist"
            ".json"         json  -> yaml  ".yaml"
            ".tmplist"      plist -> json  ".JSON-tmplist"
            ".yaml"         json  -> plist ".JSON-yaml" (yes, doesn't make much sense)

        Whether the parser is considered valid is determined from the
        extension, the extension + appendix or the view's base scope (or in a
        special case with plist using the file's xml header).
        This is also used to auto-detect the file type if the source parameter
        is omitted.

        The different dumpers try to validate the data passed.
        This works well for json -> anything because json only defines
        strings, numbers, lists and objects (dicts, arrays, hash tables).
    """
    def run(self, source_format=None, target_format=None, *args, **kwargs):
        # If called as a text command...
        self.window = self.window or sublime.active_window()

        # Check the environment (view, args, ...)
        if self.view.is_scratch():
            return

        if self.view.is_dirty():
            # While it works without saving you should always save your files
            return self.status("Please safe the file.")

        file_path = self.view.file_name()
        if not file_path or not os.path.exists(file_path):
            # REVIEW: It is not really necessary for the file to exist
            return self.status("File does not exist.", file_path)

        if source_format and target_format == source_format:
            return self.status(
                "Target and source file format are identical. (%s)" %
                target_format)

        if source_format and not source_format in loaders.get:
            return self.status("%s for '%s' not supported/implemented." %
                               ("Loader", source_format))

        if target_format and not target_format in dumpers.get:
            return self.status("%s for '%s' not supported/implemented." %
                               ("Dumper", target_format))

        # Now the actual "building" starts
        output = OutputPanel(self.window, "aaa_package_dev")
        output.show()

        # Auto-detect the file type if it's not specified
        if not source_format:
            output.write("Input type not specified, auto-detecting...")
            for Loader in loaders.get.values():
                if Loader.file_is_valid(self.view):
                    source_format = Loader.ext
                    output.write_line(' %s\n' % Loader.name)
                    break

            if not source_format:
                output.write_line("\nCould not detect file type.")
                return
            elif target_format == source_format:
                output.write_line("File already is %s." %
                                  loaders.get[source_format].name)
                return

        # Load inline options
        opts = loaders.get[source_format].load_options(self.view)

        if not target_format:
            output.write("No target format specified, searching in file...")

            if not opts or not 'target_format' in opts:
                output.write_line("\nCould not detect target format.")
                output.write_line("Please select or define a target format.")
                # Show overlay and possibly recall the whole command because it's the most simple way
                self.window.run_command(
                    'show_overlay',
                    dict(overlay="command_palette", text="Build: "))
                return

            target_format = opts["target_format"]
            # Validate the shit again, this time print to output panel
            if source_format is not None and target_format == source_format:
                return output.write_line(
                    "\nTarget and source file format are identical. (%s)" %
                    target_format)

            if not target_format in dumpers.get:
                return output.write_line(
                    "\n%s for '%s' not supported/implemented." %
                    ("Dumper", target_format))

            output.write_line(' %s\n' % dumpers.get[target_format].name)

        start_time = time.time()

        # Init the Loader
        loader = loaders.get[source_format](self.window,
                                            self.view,
                                            output=output)

        data = None
        try:
            data = loader.load(*args, **kwargs)
        except NotImplementedError, e:
            # use NotImplementedError to make the handler report the message as it pleases
            outout.write_line(str(e))
            self.status(str(e), file_path)

        if data:
            # Determine new file name
            if opts and 'ext' in opts:
                new_ext = '.' + opts["ext"]
            else:
                new_ext, prepend_target_format = loader.new_file_ext()
                if prepend_target_format:
                    new_ext = ".%s-%s" % (target_format.upper(), new_ext[1:])

            new_file_path = file_path_tuple(file_path).no_ext + (
                new_ext or '.' + target_format)

            # Init the Dumper
            dumper = dumpers.get[target_format](self.window,
                                                self.view,
                                                new_file_path,
                                                output=output)
            if dumper.dump(data, *args, **kwargs):
                self.status("File conversion successful. (%s -> %s)" %
                            (source_format, target_format))

        # Finish
        output.write("[Finished in %.3fs]" % (time.time() - start_time))
        output.finish()
예제 #5
0
    def run(self,
            edit=None,
            source_format=None,
            target_format=None,
            ext=None,
            open_new_file=False,
            rearrange_yaml_syntax_def=False,
            _output=None,
            *args,
            **kwargs):
        """Available parameters:

            edit (sublime.Edit) = None
                The edit parameter from TextCommand. Unused.

            source_format (str) = None
                The source format. Any of "yaml", "plist" or "json".
                If `None`, attempt to automatically detect the format by extension, used syntax
                highlight or (with plist) the actual contents.

            target_format (str) = None
                The target format. Any of "yaml", "plist" or "json".
                If `None`, attempt to find an option set in the file to parse.
                If unable to find an option, ask the user directly with all available format options.

            ext (str) = None
                The extension of the file to convert to, without leading dot. If `None`, the extension
                will be automatically determined by a special algorythm using "appendixes".

                Here are a few examples:
                    ".YAML-ppplist" yaml  -> plist ".ppplist"
                    ".json"         json  -> yaml  ".yaml"
                    ".tmplist"      plist -> json  ".JSON-tmplist"
                    ".yaml"         json  -> plist ".JSON-yaml" (yes, doesn't make much sense)

            open_new_file (bool) = False
                Open the (newly) created file in a new buffer.

            rearrange_yaml_syntax_def (bool) = False
                Interesting for language definitions, will automatically run
                "rearrange_yaml_syntax_def" command on it, if the target format is "yaml".
                Overrides "open_new_file" parameter.

            _output (OutputPanel) = None
                For internal use only.

            *args
                Forwarded to pretty much every relevant call but does not have any effect.
                You can't pass *args in commands anyway.

            **kwargs
                Will be forwarded to both the loading function and the dumping function, after stripping
                unsopported entries. Only do this if you know what you're doing.

                Functions in question:
                    yaml.dump
                    json.dump
                    plist.writePlist (does not support any parameters)

                A more detailed description of each supported parameter for the respective dumper can be
                found in `fileconv/dumpers.py`.
        """
        # TODO: Ditch *args, can't be passed in commands anyway

        # Check the environment (view, args, ...)
        if self.view.is_scratch():
            return

        if self.view.is_dirty():
            # While it works without saving you should always save your files
            return self.status("Please save the file.")

        file_path = self.view.file_name()
        if not file_path or not os.path.exists(file_path):
            # REVIEW: It is not really necessary for the file to exist, technically
            return self.status("File does not exist.", file_path)

        if source_format and target_format == source_format:
            return self.status(
                "Target and source file format are identical. (%s)" %
                target_format)

        if source_format and not source_format in loaders.get:
            return self.status("%s for '%s' not supported/implemented." %
                               ("Loader", source_format))

        if target_format and not target_format in dumpers.get:
            return self.status("%s for '%s' not supported/implemented." %
                               ("Dumper", target_format))

        # Now the actual "building" starts (collecting remaining parameters)
        output = _output or OutputPanel(self.window, "package_dev")
        output.show()

        # Auto-detect the file type if it's not specified
        if not source_format:
            output.write("Input type not specified, auto-detecting...")
            for Loader in loaders.get.values():
                if Loader.file_is_valid(self.view):
                    source_format = Loader.ext
                    output.write_line(' %s\n' % Loader.name)
                    break

            if not source_format:
                return output.write_line("\nUnable to detect file type.")
            elif target_format == source_format:
                return output.write_line("File already is %s." %
                                         loaders.get[source_format].name)

        # Load inline options
        Loader = loaders.get[source_format]
        opts = Loader.load_options(self.view)

        # Function to determine the new file extension depending on the target format
        def get_new_ext(target_format):
            if ext:
                return '.' + ext
            if opts and 'ext' in opts:
                return '.' + opts['ext']
            else:
                new_ext, prepend_target_format = Loader.get_new_file_ext(
                    self.view)
                if prepend_target_format:
                    new_ext = ".%s-%s" % (target_format.upper(), new_ext[1:])

            return (new_ext or '.' + target_format)

        path_tuple = file_path_tuple(
            file_path)  # This is the latest point possible

        if not target_format:
            output.write("No target format specified, searching in file...")

            # No information about a target format; ask for it
            if not opts or not 'target_format' in opts:
                output.write(" Could not detect target format.\n"
                             "Please select or define a target format...")

                # Show overlay with all dumping options except for the current type
                # Save stripped-down `items` for later
                options, items = [], []
                for itm in self.target_list:
                    # To not clash with function-local "target_format"
                    target_format_ = itm['kwargs']['target_format']
                    if target_format_ != source_format:
                        options.append([
                            "Convert to: %s" % itm['name'],
                            path_tuple.base_name + get_new_ext(target_format_)
                        ])
                        items.append(itm)

                def on_select(index):
                    target = items[index]
                    output.write_line(' %s\n' % target['name'])

                    kwargs.update(target['kwargs'])
                    kwargs.update(
                        dict(source_format=source_format, _output=output))
                    self.run(*args, **kwargs)

                # Forward all params to the new command call
                self.window.show_quick_panel(options, on_select)
                return

            target_format = opts['target_format']
            # Validate the shit again, but this time print to output panel
            if source_format is not None and target_format == source_format:
                return output.write_line(
                    "\nTarget and source file format are identical. (%s)" %
                    target_format)

            if not target_format in dumpers.get:
                return output.write_line(
                    "\n%s for '%s' not supported/implemented." %
                    ("Dumper", target_format))

            output.write_line(' %s\n' % dumpers.get[target_format].name)

        start_time = time.time()

        # Okay, THIS is where the building really starts
        # Note: loader or dumper errors are not caught in order to receive a nice traceback in the console
        loader = Loader(self.window, self.view, output=output)

        data = None
        try:
            data = loader.load(*args, **kwargs)
        except NotImplementedError as e:
            # use NotImplementedError to make the handler report the message as it pleases
            output.write_line(str(e))
            self.status(str(e), file_path)

        if data:
            # Determine new file name
            new_file_path = path_tuple.no_ext + get_new_ext(target_format)

            # Init the Dumper
            dumper = dumpers.get[target_format](self.window,
                                                self.view,
                                                new_file_path,
                                                output=output)
            if dumper.dump(data, *args, **kwargs):
                self.status("File conversion successful. (%s -> %s)" %
                            (source_format, target_format))

        # Finish
        output.write("[Finished in %.3fs]" % (time.time() - start_time))
        output.finish()

        if open_new_file or rearrange_yaml_syntax_def:
            new_view = self.window.open_file(new_file_path)

            if rearrange_yaml_syntax_def:
                # For some reason, ST would still indicate the new buffer having "usaved changes"
                # even though there aren't any (calling "save" command here).
                new_view.run_command("rearrange_yaml_syntax_def",
                                     {"save": True})