def __init__(self,
                 window,
                 view,
                 file_path=None,
                 output=None,
                 *args,
                 **kwargs):
        """Mirror the parameters to ``self``, do "init" stuff.
        """
        super(LoaderProto,
              self).__init__()  # object.__init__ takes no parameters

        self.window = window or view.window() or sublime.active_window()
        self.view = view
        self.file_path = file_path or view.file_name()

        path = os.path.split(self.file_path)[0]
        if isinstance(output, OutputPanel):
            output.set_path(path, self.file_regex)
            self.output = output
        else:
            self.output = OutputPanel(self.window,
                                      self.output_panel_name,
                                      file_regex=self.file_regex,
                                      path=path)
Beispiel #2
0
class YAMLOrderedTextDumper(dumpers.YAMLDumper):
    default_params = dict(Dumper=OrderedDictSafeDumper)

    def __init__(self, window=None, output=None):
        if isinstance(output, OutputPanel):
            self.output = output
        elif window:
            self.output = OutputPanel(window, self.output_panel_name)

    def sort_keys(self, data, sort_order, sort_numeric):
        def do_sort(obj):
            od = OrderedDict()
            # The usual order
            if sort_order:
                for key in sort_order:
                    if key in obj:
                        od[key] = obj[key]
                        del obj[key]
            # The number order
            if sort_numeric:
                nums = []
                for key in obj:
                    if key.isdigit():
                        nums.append(int(key))
                nums.sort()
                for num in nums:
                    key = str(num)
                    od[key] = obj[key]
                    del obj[key]
            # The remaining stuff (in alphabetical order)
            keys = obj.keys()
            keys.sort()
            for key in keys:
                od[key] = obj[key]
                del obj[key]

            assert len(obj) == 0
            return od

        return self._validate_data(
            data, ((lambda x: isinstance(x, dict), do_sort), ))

    def dump(self,
             data,
             sort=True,
             sort_order=None,
             sort_numeric=True,
             *args,
             **kwargs):
        self.output.write_line("Sorting %s..." % self.name)
        self.output.show()
        if sort:
            data = self.sort_keys(data, sort_order, sort_numeric)
        params = self.validate_params(kwargs)
        self.output.write_line("Dumping %s..." % self.name)
        try:
            return yaml.dump(data, **params)
        except Exception, e:
            self.output.write_line("Error dumping %s: %s" % (self.name, e))
class YAMLOrderedTextDumper(dumpers.YAMLDumper):
    default_params = dict(Dumper=OrderedDictSafeDumper)

    def __init__(self, window=None, output=None):
        if isinstance(output, OutputPanel):
            self.output = output
        elif window:
            self.output = OutputPanel(window, self.output_panel_name)

    def sort_keys(self, data, sort_order, sort_numeric):
        def do_sort(obj):
            od = OrderedDict()
            # The usual order
            if sort_order:
                for key in sort_order:
                    if key in obj:
                        od[key] = obj[key]
                        del obj[key]
            # The number order
            if sort_numeric:
                nums = []
                for key in obj:
                    if key.isdigit():
                        nums.append(int(key))
                nums.sort()
                for num in nums:
                    key = str(num)
                    od[key] = obj[key]
                    del obj[key]
            # The remaining stuff
            keys = obj.keys()
            keys.sort()
            for key in keys:
                od[key] = obj[key]
                del obj[key]

            assert len(obj) == 0
            return od

        return self._validate_data(data, (
            (lambda x: isinstance(x, dict), do_sort),
        ))

    def dump(self, data, sort=True, sort_order=False, sort_numeric=True, *args, **kwargs):
        self.output.write_line("Sorting %s..." % self.name)
        self.output.show()
        if sort:
            data = self.sort_keys(data, sort_order, sort_numeric)
        params = self.validate_params(kwargs)
        self.output.write_line("Dumping %s..." % self.name)
        try:
            return yaml.dump(data, **params)
        except Exception, e:
            self.output.write_line("Error dumping %s: %s" % (self.name, e))
Beispiel #4
0
    def __init__(self,
                 window,
                 view,
                 new_file_path,
                 output=None,
                 file_path=None,
                 *args,
                 **kwargs):
        """Guess what this does.
        """
        self.window = window
        self.view = view
        self.file_path = file_path or view.file_name()
        self.new_file_path = new_file_path

        if isinstance(output, OutputPanel):
            self.output = output
        elif window:
            self.output = OutputPanel(window, self.output_panel_name)
Beispiel #5
0
    def __init__(self, window, view, new_file_path, output=None, file_path=None, *args, **kwargs):
        """Guess what this does.
        """
        self.window = window
        self.view = view
        self.file_path = file_path or view.file_name()
        self.new_file_path = new_file_path

        if isinstance(output, OutputPanel):
            self.output = output
        elif window:
            self.output = OutputPanel(window, self.output_panel_name)
Beispiel #6
0
    def run(self, edit, sort=True, sort_numeric=True, sort_order=None, remove_single_maps=True, **kwargs):
        # Check the environment (view, args, ...)
        if self.view.is_scratch():
            return

        # Collect parameters
        file_path = self.view.file_name()
        if sort_order is None:
            sort_order = self.sort_order
        vp = get_viewport_coords(self.view)

        output = OutputPanel(self.view.window() or sublime.active_window(), "aaa_package_dev")
        output.show()

        self.start_time = time.time()

        # Init the Loader
        loader = loaders.YAMLLoader(None, self.view, file_path=file_path, output=output)

        data = None
        try:
            data = loader.load()
        except NotImplementedError, e:
            # use NotImplementedError to make the handler report the message as it pleases
            output.write_line(str(e))
            self.status(str(e), file_path)
Beispiel #7
0
    def __init__(self, window, view, file_path=None, output=None, *args, **kwargs):
        """Mirror the parameters to ``self``, do "init" stuff.
        """
        super(LoaderProto, self).__init__()  # object.__init__ takes no parameters

        self.window = window or view.window() or sublime.active_window()
        self.view = view
        self.file_path = file_path or view.file_name()

        path = os.path.split(self.file_path)[0]
        if isinstance(output, OutputPanel):
            output.set_path(path, self.file_regex)
            self.output = output
        else:
            self.output = OutputPanel(self.window, self.output_panel_name, file_regex=self.file_regex, path=path)
Beispiel #8
0
class DumperProto(object):
    """Prototype class for data dumpers of different types.

        Classes derived from this class (and in this file) will be appended
        to the module's ``get`` variable (a dict) with ``self.ext`` as their key.

        Variables to be defined:

            name (str)
                The dumpers name, e.g. "JSON" or "Property List".

            ext (str)
                The default file extension.

            output_panel_name (str; optional)
                If this is specified it will be used as the output panel's
                reference name.
                Defaults to ``"aaa_package_dev"``.

            default_params (dict; optional)
                Just a dict of the default params for self.write().

            allowed_params (tuple; optional)
                A collection of strings defining the allowed parameters for
                self.write(). Other keys in the kwargs dict will be removed.


        Methods to be implemented:

            write(self, data, params, *args, **kwargs)
                This is called when the actual parsing should happen.

                Data to write is defined in ``data``.
                The parsed data should be returned.
                To output problems, use ``self.output.write_line(str)``.
                The default self.dump function will catch excetions raised
                and print them via ``str()`` to the output.

                Parameters to the dumping functions are in ``params`` dict,
                which have been validated before, according to the class
                variables (see above).
                *args, **kwargs parameters are passed from
                ``load(self, *args, **kwargs)``. If you want to specify or
                process any options or optional parsing, use these.

            validate_data(self, data, *args, **kwargs) (optional)

                Called by self.dump. Please read the documentation for
                _validate_data in order to understand how this function works.

        Methods you can override/implement
        (please read their documentation/code to understand their purposes):

            _validate_data(self, data, funcs)

            validate_params(self, params)

            dump(self, *args, **kwargs)
    """
    name = ""
    ext  = ""
    output_panel_name = "aaa_package_dev"
    default_params = {}
    allowed_params = ()

    def __init__(self, window, view, new_file_path, output=None, file_path=None, *args, **kwargs):
        """Guess what this does.
        """
        self.window = window
        self.view = view
        self.file_path = file_path or view.file_name()
        self.new_file_path = new_file_path

        if isinstance(output, OutputPanel):
            self.output = output
        elif window:
            self.output = OutputPanel(window, self.output_panel_name)

    def validate_data(self, data, *args, **kwargs):
        """To be implemented (optional).

            Must return the validated data object.

            Example:
                return self._validate_data(data, [
                    ((lambda x: isinstance(x, float), int),
                     (lambda x: isinstance(x, datetime.datetime), str),
                     (lambda x: x is None, False))
                ]
        """
        pass

    def _validate_data(self, data, funcs):
        """Check for incompatible data recursively.

        ``funcs`` is supposed to be a set, or just iterable two times and
        represents two functions, one to test whether the data is invalid
        and one to validate it. Both functions accept one parameter:
        the object to test.
        The validation value can be a function (is callable) or be a value.
        In the latter case the value will always be used instead of the
        previous object.

        Example:
            funcs = ((lambda x: isinstance(x, float), int),
                     (lambda x: isinstance(x, datetime.datetime), str),
                     (lambda x: x is None, False))
        """
        checked = []

        def check_recursive(obj):
            # won't and shouldn't work for immutable types
            # I mean, why would you even reference objects inside themselves?
            if obj in checked:
                return obj
            checked.append(obj)

            for is_invalid, validate in funcs:
                if is_invalid(obj):
                    if callable(validate):
                        obj = validate(obj)
                    else:
                        obj = validate

            if isinstance(obj, dict):  # dicts are fine
                for key in obj:
                    obj[key] = check_recursive(obj[key])

            if isinstance(obj, list):  # lists are too
                for i in range(len(obj)):
                    obj[i] = check_recursive(obj[i])

            if isinstance(obj, tuple):  # tuples are immutable ...
                return tuple([check_recursive(sub_obj) for sub_obj in obj])

            if isinstance(obj, set):  # sets ...
                for val in obj:
                    new_val = check_recursive(val)
                    if new_val != val:  # a set's components are hashable, no need to "is"
                        obj.remove(val)
                        obj.add(new_val)

            return obj

        return check_recursive(data)

    def validate_params(self, params):
        """Validate the parameters according to self.default_params and
        self.allowed_params.
        """
        new_params = self.default_params.copy()
        new_params.update(params)
        for key in new_params.keys():
            if key not in self.allowed_params:
                del new_params[key]
        return new_params

    def dump(self, data, *args, **kwargs):
        """Wraps the ``self.write`` function.

        This function is called by the handler directly.
        """
        self.output.write_line("Writing %s... (%s)" % (self.name, self.new_file_path))
        self.output.show()
        data = self.validate_data(data)
        params = self.validate_params(kwargs)
        try:
            self.write(data, params, *args, **kwargs)
        except Exception, e:
            self.output.write_line("Error writing %s: %s" % (self.name, e))
        else:
Beispiel #9
0
    def run(self,
            edit,
            sort=True,
            sort_numeric=True,
            sort_order=None,
            remove_single_line_maps=True,
            insert_newlines=True,
            save=False,
            **kwargs):
        """Available parameters:

            sort (bool) = True
                Whether the list should be sorted at all. If this is not
                ``True`` the dicts' keys' order is likely not going to equal
                the input.

            sort_numeric (bool) = True
                A language definition's captures are assigned to a numeric key
                which is in fact a string. If you want to bypass the string
                sorting and instead sort by the strings number value, you will
                want this to be ``True``.

            sort_order (list)
                When this is passed it will be used instead of the default.
                The first element in the list will be the first key to be
                written for ALL dictionaries.
                Set to ``False`` to skip this step.

                The default order is:
                comment, name, scopeName, fileTypes, uuid, begin,
                beginCaptures, end, endCaptures, match, captures, include,
                patterns, repository

            remove_single_line_maps (bool) = True
                This will in fact turn the "- {include: '#something'}" lines
                into "- include: '#something'".
                Also splits mappings like "- {name: anything, match: .*}" to
                multiple lines.

                Be careful though because this is just a
                simple regexp that's safe for *usual* syntax definitions.

            insert_newlines (bool) = True
                Add newlines where appropriate to make the whole data appear
                better organized.

                Essentially add a new line:
                - before global "patterns" key
                - before global "repository" key
                - before every repository key except for the first

            save (bool) = False
                Save the view after processing is done.

            **kwargs
                Forwarded to yaml.dump (if they are valid).
        """
        # Check the environment (view, args, ...)
        if self.view.is_scratch():
            return
        if self.view.is_loading():
            # The view has not yet loaded, recall the command in this case until ST is done
            kwargs.update(
                dict(sort=sort,
                     sort_numeric=sort_numeric,
                     sort_order=sort_order,
                     remove_single_line_maps=remove_single_line_maps,
                     insert_newlines=insert_newlines,
                     save=save))
            sublime.set_timeout(
                lambda: self.view.run_command("rearrange_yaml_syntax_def",
                                              kwargs), 20)
            return

        # Collect parameters
        file_path = self.view.file_name()
        if sort_order is None:
            sort_order = self.default_order
        vp = get_viewport_coords(self.view)

        output = OutputPanel(self.view.window() or sublime.active_window(),
                             "aaa_package_dev")
        output.show()

        self.start_time = time.time()

        # Init the Loader
        loader = loaders.YAMLLoader(None,
                                    self.view,
                                    file_path=file_path,
                                    output=output)

        data = None
        try:
            data = loader.load()
        except NotImplementedError, e:
            # Use NotImplementedError to make the handler report the message as it pleases
            output.write_line(str(e))
            self.status(str(e), file_path)
    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})
    def run(self, edit, sort=True, sort_numeric=True, sort_order=None, remove_single_line_maps=True,
            insert_newlines=True, save=False, **kwargs):
        """Available parameters:

            sort (bool) = True
                Whether the list should be sorted at all. If this is not
                ``True`` the dicts' keys' order is likely not going to equal
                the input.

            sort_numeric (bool) = True
                A language definition's captures are assigned to a numeric key
                which is in fact a string. If you want to bypass the string
                sorting and instead sort by the strings number value, you will
                want this to be ``True``.

            sort_order (list)
                When this is passed it will be used instead of the default.
                The first element in the list will be the first key to be
                written for ALL dictionaries.
                Set to ``False`` to skip this step.

                The default order is:
                comment, name, scopeName, fileTypes, uuid, begin,
                beginCaptures, end, endCaptures, match, captures, include,
                patterns, repository

            remove_single_line_maps (bool) = True
                This will in fact turn the "- {include: '#something'}" lines
                into "- include: '#something'".
                Also splits mappings like "- {name: anything, match: .*}" to
                multiple lines.

                Be careful though because this is just a
                simple regexp that's safe for *usual* syntax definitions.

            insert_newlines (bool) = True
                Add newlines where appropriate to make the whole data appear
                better organized.

                Essentially add a new line:
                - before global "patterns" key
                - before global "repository" key
                - before every repository key except for the first

            save (bool) = False
                Save the view after processing is done.

            **kwargs
                Forwarded to yaml.dump (if they are valid).
        """
        # Check the environment (view, args, ...)
        if self.view.is_scratch():
            return
        if self.view.is_loading():
            # The view has not yet loaded, recall the command in this case until ST is done
            kwargs.update(dict(
                sort=sort,
                sort_numeric=sort_numeric,
                sort_order=sort_order,
                remove_single_line_maps=remove_single_line_maps,
                insert_newlines=insert_newlines,
                save=save
            ))
            sublime.set_timeout(
                lambda: self.view.run_command("rearrange_yaml_syntax_def", kwargs),
                20
            )
            return

        # Collect parameters
        file_path = self.view.file_name()
        if sort_order is None:
            sort_order = self.default_order
        vp = get_viewport_coords(self.view)

        output = OutputPanel(self.view.window() or sublime.active_window(), "aaa_package_dev")
        output.show()

        self.start_time = time.time()

        # Init the Loader
        loader = loaders.YAMLLoader(None, self.view, file_path=file_path, output=output)

        data = None
        try:
            data = loader.load()
        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 not data:
            output.write_line("No contents in file.")
            return self.finish(output)

        # Dump
        dumper = YAMLOrderedTextDumper(output=output)
        if remove_single_line_maps:
            kwargs["Dumper"] = YAMLLanguageDevDumper
        text = dumper.dump(data, sort, sort_order, sort_numeric, **kwargs)
        if not text:
            self.status("Error re-dumping the data.")
            return

        # Replace the whole buffer (with default options)
        self.view.replace(
            edit,
            sublime.Region(0, self.view.size()),
            "# [PackageDev] target_format: plist, ext: tmLanguage\n"
            + text
        )

        # Insert the new lines using the syntax definition (which has hopefully been set)
        if insert_newlines:
            find = self.view.find_by_selector

            def select(l, only_first=True, not_first=True):
                # 'only_first' has priority
                if not l:
                    return l  # empty
                elif only_first:
                    return l[:1]
                elif not_first:
                    return l[1:]
                return l

            def filter_pattern_regs(reg):
                # Only use those keys where the region starts at column 0 and begins with '-'
                # because these are apparently the first keys of a 1-scope list
                beg = reg.begin()
                return self.view.rowcol(beg)[1] == 0 and self.view.substr(beg) == '-'

            regs = (
                select(find('meta.patterns - meta.repository-block'))
                + select(find('meta.repository-block'))
                + select(find('meta.repository-block meta.repository-key'), False)
                + select(list(filter(filter_pattern_regs, find('meta'))), False)
            )

            # Iterate in reverse order to not clash the regions because we will be modifying the source
            regs.sort()
            regs.reverse()
            for reg in regs:
                self.view.insert(edit, reg.begin(), '\n')

        if save:
            self.view.run_command("save")
            output.write_line("File saved")

        # Finish
        set_viewport(self.view, vp)
        self.finish(output)
Beispiel #12
0
class DumperProto(object):
    """Prototype class for data dumpers of different types.

        Classes derived from this class (and in this file) will be appended
        to the module's ``get`` variable (a dict) with ``self.ext`` as their key.

        Variables to be defined:

            name (str)
                The dumpers name, e.g. "JSON" or "Property List".

            ext (str)
                The default file extension.

            output_panel_name (str; optional)
                If this is specified it will be used as the output panel's
                reference name.
                Defaults to ``"aaa_package_dev"``.

            default_params (dict; optional)
                Just a dict of the default params for self.write().

            allowed_params (tuple; optional)
                A collection of strings defining the allowed parameters for
                self.write(). Other keys in the kwargs dict will be removed.


        Methods to be implemented:

            write(self, data, params, *args, **kwargs)
                This is called when the actual parsing should happen.

                Data to write is defined in ``data``.
                The parsed data should be returned.
                To output problems, use ``self.output.write_line(str)``.
                The default self.dump function will catch excetions raised
                and print them via ``str()`` to the output.

                Parameters to the dumping functions are in ``params`` dict,
                which have been validated before, according to the class
                variables (see above).
                *args, **kwargs parameters are passed from
                ``load(self, *args, **kwargs)``. If you want to specify or
                process any options or optional parsing, use these.

            validate_data(self, data, *args, **kwargs) (optional)

                Called by self.dump. Please read the documentation for
                _validate_data in order to understand how this function works.

        Methods you can override/implement
        (please read their documentation/code to understand their purposes):

            _validate_data(self, data, funcs)

            validate_params(self, params)

            dump(self, *args, **kwargs)
    """
    name = ""
    ext = ""
    output_panel_name = "aaa_package_dev"
    default_params = {}
    allowed_params = ()

    def __init__(self,
                 window,
                 view,
                 new_file_path,
                 output=None,
                 file_path=None,
                 *args,
                 **kwargs):
        """Guess what this does.
        """
        self.window = window
        self.view = view
        self.file_path = file_path or view.file_name()
        self.new_file_path = new_file_path

        if isinstance(output, OutputPanel):
            self.output = output
        elif window:
            self.output = OutputPanel(window, self.output_panel_name)

    def validate_data(self, data, *args, **kwargs):
        """To be implemented (optional).

            Must return the validated data object.

            Example:
                return self._validate_data(data, [
                    ((lambda x: isinstance(x, float), int),
                     (lambda x: isinstance(x, datetime.datetime), str),
                     (lambda x: x is None, False))
                ]
        """
        pass

    def _validate_data(self, data, funcs):
        """Check for incompatible data recursively.

        ``funcs`` is supposed to be a set, or just iterable two times and
        represents two functions, one to test whether the data is invalid
        and one to validate it. Both functions accept one parameter:
        the object to test.
        The validation value can be a function (is callable) or be a value.
        In the latter case the value will always be used instead of the
        previous object.

        Example:
            funcs = ((lambda x: isinstance(x, float), int),
                     (lambda x: isinstance(x, datetime.datetime), str),
                     (lambda x: x is None, False))
        """
        checked = []

        def check_recursive(obj):
            # won't and shouldn't work for immutable types
            # I mean, why would you even reference objects inside themselves?
            if obj in checked:
                return obj
            checked.append(obj)

            for is_invalid, validate in funcs:
                if is_invalid(obj):
                    if callable(validate):
                        obj = validate(obj)
                    else:
                        obj = validate

            if isinstance(obj, dict):  # dicts are fine
                for key in obj:
                    obj[key] = check_recursive(obj[key])

            if isinstance(obj, list):  # lists are too
                for i in range(len(obj)):
                    obj[i] = check_recursive(obj[i])

            if isinstance(obj, tuple):  # tuples are immutable ...
                return tuple([check_recursive(sub_obj) for sub_obj in obj])

            if isinstance(obj, set):  # sets ...
                for val in obj:
                    new_val = check_recursive(val)
                    if new_val != val:  # a set's components are hashable, no need to "is"
                        obj.remove(val)
                        obj.add(new_val)

            return obj

        return check_recursive(data)

    def validate_params(self, params):
        """Validate the parameters according to self.default_params and
        self.allowed_params.
        """
        new_params = self.default_params.copy()
        new_params.update(params)
        for key in new_params.keys():
            if key not in self.allowed_params:
                del new_params[key]
        return new_params

    def dump(self, data, *args, **kwargs):
        """Wraps the ``self.write`` function.

        This function is called by the handler directly.
        """
        self.output.write_line("Writing %s... (%s)" %
                               (self.name, self.new_file_path))
        self.output.show()
        data = self.validate_data(data)
        params = self.validate_params(kwargs)
        try:
            self.write(data, params, *args, **kwargs)
        except Exception, e:
            self.output.write_line("Error writing %s: %s" % (self.name, e))
        else:
Beispiel #13
0
    def run(self, edit, sort=True, sort_numeric=True, sort_order=None, remove_single_line_maps=True,
            insert_newlines=True, save=False, **kwargs):
        """Available parameters:

            sort (bool) = True
                Whether the list should be sorted at all. If this is not
                ``True`` the dicts' keys' order is likely not going to equal
                the input.

            sort_numeric (bool) = True
                A language definition's captures are assigned to a numeric key
                which is in fact a string. If you want to bypass the string
                sorting and instead sort by the strings number value, you will
                want this to be ``True``.

            sort_order (list)
                When this is passed it will be used instead of the default.
                The first element in the list will be the first key to be
                written for ALL dictionaries.
                Set to ``False`` to skip this step.

                The default order is:
                comment, name, scopeName, fileTypes, uuid, begin,
                beginCaptures, end, endCaptures, match, captures, include,
                patterns, repository

            remove_single_line_maps (bool) = True
                This will in fact turn the "- {include: '#something'}" lines
                into "- include: '#something'".
                Also splits mappings like "- {name: anything, match: .*}" to
                multiple lines.

                Be careful though because this is just a
                simple regexp that's safe for *usual* syntax definitions.

            insert_newlines (bool) = True
                Add newlines where appropriate to make the whole data appear
                better organized.

                Essentially add a new line:
                - before global "patterns" key
                - before global "repository" key
                - before every repository key except for the first

            save (bool) = False
                Save the view after processing is done.

            **kwargs
                Forwarded to yaml.dump (if they are valid).
        """
        # Check the environment (view, args, ...)
        if self.view.is_scratch():
            return
        if self.view.is_loading():
            # The view has not yet loaded, recall the command in this case until ST is done
            kwargs.update(dict(
                sort=sort,
                sort_numeric=sort_numeric,
                sort_order=sort_order,
                remove_single_line_maps=remove_single_line_maps,
                insert_newlines=insert_newlines,
                save=save
            ))
            sublime.set_timeout(
                lambda: self.view.run_command("rearrange_yaml_syntax_def", kwargs),
                20
            )
            return

        # Collect parameters
        file_path = self.view.file_name()
        if sort_order is None:
            sort_order = self.default_order
        vp = get_viewport_coords(self.view)

        output = OutputPanel(self.view.window() or sublime.active_window(), "aaa_package_dev")
        output.show()

        self.start_time = time.time()

        # Init the Loader
        loader = loaders.YAMLLoader(None, self.view, file_path=file_path, output=output)

        data = None
        try:
            data = loader.load()
        except NotImplementedError, e:
            # Use NotImplementedError to make the handler report the message as it pleases
            output.write_line(str(e))
            self.status(str(e), file_path)
Beispiel #14
0
    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)
Beispiel #15
0
 def __init__(self, window=None, output=None):
     if isinstance(output, OutputPanel):
         self.output = output
     elif window:
         self.output = OutputPanel(window, self.output_panel_name)
Beispiel #16
0
class LoaderProto(object):
    """Prototype class for data loaders of different types.

        Classes derived from this class (and in this file) will be appended
        to the module's ``get`` variable (a dict) with ``self.ext`` as their key.

        Variables to define:

            name (str)
                The loaders name, e.g. "JSON" or "Property List".

            ext (str)
                The default file extension. Used to construct ``self.ext_regex``.

            comment (str; optional)
                The line comment string for the file type. Used to construct
                ``self.opt_regex``.

            scope (str; optional)
                If the view's base scope equals this the file will be considered
                "valid" and then parsed.

            file_regex (str; optional)
                Regex to be applied to your output string in ``parse()``.

                This is used to determine the problem's position in the file and
                lets the user browse the errors with F4 and Shift+F4.
                Define up to three groups:
                    1: file path
                    2: line number
                    3: column

                For reference, see the "result_file_regex" key in a view's
                settings() or compare to build systems.

            output_panel_name (str; optional)
                If this is specified it will be used as the output panel's
                reference name.

                Defaults to ``"aaa_package_dev"``.

            ext_regex (str; optional)
                This regex will be used by get_ext_appendix() to determine the
                extension's appendix. The appendix should be found in group 1.

                Defaults to ``r'(?i)\.%s(?:-([^\.]+))?$' % self.ext``.

            opt_regex (str; optional)
                A regex to search for an option dict in a line. Used by
                ``self.get_options()`` and ``cls.load_options(view)``.
                Content should be found in group 1.

                Defaults to ``r'^\s*%s\s+\[PackageDev\]\s+(.+)$' % cls.comment``.


        Methods to implement:

            parse(self, *args, **kwargs)
                This is called when the actual parsing should happen.

                The file to be read from is defined in ``self.file_path``.
                The parsed data should be returned.
                To output problems, use ``self.output.write_line(str)`` and use a
                string matched by ``self.file_regex`` if possible.

                *args, **kwargs parameters are passed from
                ``load(self, *args, **kwargs)``. If you want to specify any options
                or optional parsing, use these.

        Methods you can override/implement
        (please read their documentation/code to understand their purposes):

            @classmethod
            _pre_init_(cls)

            @classmethod
            get_ext_appendix(cls, file_name)

            @classmethod
            get_new_file_ext(cls, view, file_name=None)

            new_file_ext(self)

            @classmethod
            load_options(self, view)

            get_options(self)

            @classmethod
            file_is_valid(cls, view, file_name=None)

            is_valid(self)

            load(self, *args, **kwargs)
    """
    name    = ""
    ext     = ""
    comment = ""
    scope   = None
    file_regex = ""
    output_panel_name = "aaa_package_dev"

    def __init__(self, window, view, file_path=None, output=None, *args, **kwargs):
        """Mirror the parameters to ``self``, do "init" stuff.
        """
        super(LoaderProto, self).__init__()  # object.__init__ takes no parameters

        self.window = window or view.window() or sublime.active_window()
        self.view = view
        self.file_path = file_path or view.file_name()

        path = os.path.split(self.file_path)[0]
        if isinstance(output, OutputPanel):
            output.set_path(path, self.file_regex)
            self.output = output
        else:
            self.output = OutputPanel(self.window, self.output_panel_name, file_regex=self.file_regex, path=path)
            self.output.clear()

    @classmethod
    def _pre_init_(cls):
        """Assign attributes that depend on other attributes defined by subclasses.
        """
        if not hasattr(cls, 'ext_regex'):
            cls.ext_regex = r'(?i)\.%s(?:-([^\.]+))?$' % cls.ext

        if not hasattr(cls, 'opt_regex'):
            # Will result in an exception when running cls.load_options but will be caught.
            cls.opt_regex = cls.comment and r'^\s*%s\s+\[PackageDev\]\s+(.+)$' % cls.comment or ""

    @classmethod
    def get_ext_appendix(cls, file_name):
        """Returns the appendix part of a file_name in style ".json-Appendix",
        "json" being ``self.ext`` respectively, or ``None``.
        """
        if file_name:
            ret = re.search(cls.ext_regex, file_name)
            if ret and ret.group(1):
                return ret.group(1)
        return None

    @classmethod
    def get_new_file_ext(cls, view, file_path=None):
        """Returns a tuple in style (str(ext), bool(prepend_ext)).

        The first part is the extension string, which may be ``None``.
        The second part is a boolean value that indicates whether the dumper
        (or the handler) should use the value of the first part as appendix
        and prepend the actual "new" file type.

        See also get_ext_appendix().
        """
        file_path = file_path or view and view.file_name()
        if not file_path:
            return (None, False)

        appendix = cls.get_ext_appendix(file_path)
        if appendix:
            return ('.' + appendix, False)

        ext = os.path.splitext(file_path)[1]
        if not ext == '.' + cls.ext and cls.file_is_valid(view, file_path):
            return (ext, True)

        return (None, False)

    def new_file_ext(self):
        """Instance method wrapper for ``cls.get_new_file_ext``.
        """
        return self.__class__.get_new_file_ext(self.view, self.file_path)

    @classmethod
    def load_options(self, view):
        """Search for a line comment in the first few lines which starts with
        ``"[PackageDev]"`` and parse the following things using ``yaml.safe_load``
        after wrapping them in "{}".
        """
        # Don't bother reading the file to load its options.
        if not view:
            return None

        # Search for options in the first 3 lines (compatible with xml)
        for i in range(3):
            try:
                line = coorded_substr(view, (i, 0), (i, -1))
                optstr = re.search(self.opt_regex, line)
                # Just parse the string with yaml; wrapped in {}
                # Yeah, I'm lazy like that, but see, I even put "safe_" in front of it
                return yaml.safe_load('{%s}' % optstr.group(1))
            except:
                continue

        return None

    def get_options(self):
        """Instance method wrapper for ``cls.load_options``.
        """
        return self.__class__.load_options(self.view)

    @classmethod
    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))

    def is_valid(self):
        """Instance method wrapper for ``cls.file_is_valid``.
        """
        return self.__class__.file_is_valid(self.view, self.file_path)

    def load(self, *args, **kwargs):
        """Wraps ``self.parse(*args, **kwargs)`` and calls some other functions
        similar for almost every loader.

        This function is called by the handler directly.
        """
        if not self.is_valid():
            raise NotImplementedError("Not a %s file." % self.name)

        self.output.write_line("Parsing %s... (%s)" % (self.name, self.file_path))

        return self.parse(*args, **kwargs)

    def parse(self, *args, **kwargs):
        """To be implemented. Should return the parsed data from
        ``self.file_path`` as a Python object.
        """
        pass
    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)
Beispiel #18
0
class LoaderProto(object):
    """Prototype class for data loaders of different types.

        Classes derived from this class (and in this file) will be appended
        to the module's ``get`` variable (a dict) with ``self.ext`` as their key.

        Variables to define:

            name (str)
                The loaders name, e.g. "JSON" or "Property List".

            ext (str)
                The default file extension. Used to construct ``self.ext_regex``.

            comment (str; optional)
                The line comment string for the file type. Used to construct
                ``self.opt_regex``.

            scope (str; optional)
                If the view's base scope equals this the file will be considered
                "valid" and then parsed.

            file_regex (str; optional)
                Regex to be applied to your output string in ``parse()``.

                This is used to determine the problem's position in the file and
                lets the user browse the errors with F4 and Shift+F4.
                Define up to three groups:
                    1: file path
                    2: line number
                    3: column

                For reference, see the "result_file_regex" key in a view's
                settings() or compare to build systems.

            output_panel_name (str; optional)
                If this is specified it will be used as the output panel's
                reference name.

                Defaults to ``"aaa_package_dev"``.

            ext_regex (str; optional)
                This regex will be used by get_ext_appendix() to determine the
                extension's appendix. The appendix should be found in group 1.

                Defaults to ``r'(?i)\.%s(?:-([^\.]+))?$' % self.ext``.

            opt_regex (str; optional)
                A regex to search for an option dict in a line. Used by
                ``self.get_options()`` and ``cls.load_options(view)``.
                Content should be found in group 1.

                Defaults to ``r'^\s*%s\s+\[PackageDev\]\s+(.+)$' % cls.comment``.


        Methods to implement:

            parse(self, *args, **kwargs)
                This is called when the actual parsing should happen.

                The file to be read from is defined in ``self.file_path``.
                The parsed data should be returned.
                To output problems, use ``self.output.write_line(str)`` and use a
                string matched by ``self.file_regex`` if possible.

                *args, **kwargs parameters are passed from
                ``load(self, *args, **kwargs)``. If you want to specify any options
                or optional parsing, use these.

        Methods you can override/implement
        (please read their documentation/code to understand their purposes):

            @classmethod
            _pre_init_(cls)

            @classmethod
            get_ext_appendix(cls, file_name)

            @classmethod
            get_new_file_ext(cls, view, file_name=None)

            new_file_ext(self)

            @classmethod
            load_options(self, view)

            get_options(self)

            @classmethod
            file_is_valid(cls, view, file_name=None)

            is_valid(self)

            load(self, *args, **kwargs)
    """
    name    = ""
    ext     = ""
    comment = ""
    scope   = None
    file_regex = ""
    output_panel_name = "aaa_package_dev"

    def __init__(self, window, view, file_path=None, output=None, *args, **kwargs):
        """Mirror the parameters to ``self``, do "init" stuff.
        """
        super(LoaderProto, self).__init__()  # object.__init__ takes no parameters

        self.window = window or view.window() or sublime.active_window()
        self.view = view
        self.file_path = file_path or view.file_name()

        path = os.path.split(self.file_path)[0]
        if isinstance(output, OutputPanel):
            output.set_path(path, self.file_regex)
            self.output = output
        else:
            self.output = OutputPanel(self.window, self.output_panel_name, file_regex=self.file_regex, path=path)

    @classmethod
    def _pre_init_(cls):
        """Assign attributes that depend on other attributes defined by subclasses.
        """
        if not hasattr(cls, 'ext_regex'):
            cls.ext_regex = r'(?i)\.%s(?:-([^\.]+))?$' % cls.ext

        if not hasattr(cls, 'opt_regex'):
            # Will result in an exception when running cls.load_options but will be caught.
            cls.opt_regex = cls.comment and r'^\s*%s\s+\[PackageDev\]\s+(.+)$' % cls.comment or ""

    @classmethod
    def get_ext_appendix(cls, file_name):
        """Returns the appendix part of a file_name in style ".json-Appendix",
        "json" being ``self.ext`` respectively, or ``None``.
        """
        if file_name:
            ret = re.search(cls.ext_regex, file_name)
            if ret and ret.group(1):
                return ret.group(1)
        return None

    @classmethod
    def get_new_file_ext(cls, view, file_path=None):
        """Returns a tuple in style (str(ext), bool(prepend_ext)).

        The first part is the extension string, which may be ``None``.
        The second part is a boolean value that indicates whether the dumper
        (or the handler) should use the value of the first part as appendix
        and prepend the actual "new" file type.

        See also get_ext_appendix().
        """
        file_path = file_path or view and view.file_name()
        if not file_path:
            return (None, False)

        appendix = cls.get_ext_appendix(file_path)
        if appendix:
            return ('.' + appendix, False)

        ext = os.path.splitext(file_path)[1]
        if not ext == '.' + cls.ext and cls.file_is_valid(view, file_path):
            return (ext, True)

        return (None, False)

    def new_file_ext(self):
        """Instance method wrapper for ``cls.get_new_file_ext``.
        """
        return self.__class__.get_new_file_ext(self.view, self.file_path)

    @classmethod
    def load_options(self, view):
        """Search for a line comment in the first few lines which starts with
        ``"[PackageDev]"`` and parse the following things using ``yaml.safe_load``
        after wrapping them in "{}".
        """
        # Don't bother reading the file to load its options.
        if not view:
            return None

        # Search for options in the first 3 lines (compatible with xml)
        for i in range(3):
            try:
                line = coorded_substr(view, (i, 0), (i, -1))
                optstr = re.search(self.opt_regex, line)
                # Just parse the string with yaml; wrapped in {}
                # Yeah, I'm lazy like that, but see, I even put "safe_" in front of it
                return yaml.safe_load('{%s}' % optstr.group(1))
            except:
                continue

        return None

    def get_options(self):
        """Instance method wrapper for ``cls.load_options``.
        """
        return self.__class__.load_options(self.view)

    @classmethod
    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))

    def is_valid(self):
        """Instance method wrapper for ``cls.file_is_valid``.
        """
        return self.__class__.file_is_valid(self.view, self.file_path)

    def load(self, *args, **kwargs):
        """Wraps ``self.parse(*args, **kwargs)`` and calls some other functions
        similar for almost every loader.

        This function is called by the handler directly.
        """
        if not self.is_valid():
            raise NotImplementedError("Not a %s file." % self.name)

        self.output.write_line("Parsing %s... (%s)" % (self.name, self.file_path))

        return self.parse(*args, **kwargs)

    def parse(self, *args, **kwargs):
        """To be implemented. Should return the parsed data from
        ``self.file_path`` as a Python object.
        """
        pass
    def run(self,
            edit,
            sort=True,
            sort_numeric=True,
            sort_order=None,
            remove_single_line_maps=True,
            insert_newlines=True,
            save=False,
            _output_text=None,
            **kwargs):
        """Available parameters:

            sort (bool) = True
                Whether the list should be sorted at all. If this is not
                ``True`` the dicts' keys' order is likely not going to equal
                the input.

            sort_numeric (bool) = True
                A language definition's captures are assigned to a numeric key
                which is in fact a string. If you want to bypass the string
                sorting and instead sort by the strings number value, you will
                want this to be ``True``.

            sort_order (list)
                When this is passed it will be used instead of the default.
                The first element in the list will be the first key to be
                written for ALL dictionaries.
                Set to ``False`` to skip this step.

                The default order is:
                comment, name, scopeName, fileTypes, uuid, begin,
                beginCaptures, end, endCaptures, match, captures, include,
                patterns, repository

            remove_single_line_maps (bool) = True
                This will in fact turn the "- {include: '#something'}" lines
                into "- include: '#something'".
                Also splits mappings like "- {name: anything, match: .*}" to
                multiple lines.

                Be careful though because this is just a
                simple regexp that's safe for *usual* syntax definitions.

            insert_newlines (bool) = True
                Add newlines where appropriate to make the whole data appear
                better organized.

                Essentially add a new line:
                - before global "patterns" key
                - before global "repository" key
                - before every repository key except for the first

            save (bool) = False
                Save the view after processing is done.

            _output_text (str) = None
                Text to be prepended to the output panel since it gets cleared
                by `window.get_output_panel`.

            **kwargs
                Forwarded to yaml.dump (if they are valid).
        """
        # Check the environment (view, args, ...)
        if self.view.is_scratch():
            return
        if self.view.is_loading():
            # The view has not yet loaded, recall the command in this case until ST is done
            kwargs.update(
                dict(sort=sort,
                     sort_numeric=sort_numeric,
                     sort_order=sort_order,
                     remove_single_line_maps=remove_single_line_maps,
                     insert_newlines=insert_newlines,
                     save=save))
            sublime.set_timeout(
                lambda: self.view.run_command("rearrange_yaml_syntax_def",
                                              kwargs), 20)
            return

        # Collect parameters
        file_path = self.view.file_name()
        if sort_order is None:
            sort_order = self.default_order
        vp = get_viewport_coords(self.view)

        with OutputPanel(self.view.window() or sublime.active_window(),
                         "aaa_package_dev") as output:
            output.show()
            if _output_text:
                output.write_line(_output_text)  # With additional newline

            self.start_time = time.time()

            # Init the Loader
            loader = loaders.YAMLLoader(None,
                                        self.view,
                                        file_path=file_path,
                                        output=output)

            data = None
            try:
                data = loader.load(**kwargs)
            except:
                output.write_line("Unexpected error occured while parsing, "
                                  "please see the console for details.")
                raise

            if not data:
                output.write_line("No contents in file.")
                return

            # Dump
            dumper = YAMLOrderedTextDumper(output=output)
            if remove_single_line_maps:
                kwargs["Dumper"] = YAMLLanguageDevDumper

            try:
                text = dumper.dump(data, sort, sort_order, sort_numeric,
                                   **kwargs)
            except:
                output.write_line("Unexpected error occured while dumping, "
                                  "please see the console for details.")
                raise

            if not text:
                output.write_line(
                    "Error re-dumping the data in file (no output).")
                self.status("Error re-dumping the data (no output).")
                return

            # Replace the whole buffer (with default options)
            self.view.replace(
                edit, sublime.Region(0, self.view.size()),
                "# [PackageDev] target_format: plist, ext: tmLanguage\n" +
                text)

            # Insert the new lines using the syntax definition (which has hopefully been set)
            if insert_newlines:
                output.write_line("Inserting newlines...")
                find = self.view.find_by_selector

                def select(l, only_first=True, not_first=True):
                    # 'only_first' has priority
                    if not l:
                        return l  # empty
                    elif only_first:
                        return l[:1]
                    elif not_first:
                        return l[1:]
                    return l

                def filter_pattern_regs(reg):
                    # Only use those keys where the region starts at column 0 and begins with '-'
                    # because these are apparently the first keys of a 1-scope list
                    beg = reg.begin()
                    return self.view.rowcol(beg)[1] == 0 and self.view.substr(
                        beg) == '-'

                regs = (select(find('meta.patterns - meta.repository-block')) +
                        select(find('meta.repository-block')) + select(
                            find('meta.repository-block meta.repository-key'),
                            False) +
                        select(list(filter(filter_pattern_regs, find('meta'))),
                               False))

                # Iterate in reverse order to not clash the regions because we will be modifying the source
                regs.sort()
                regs.reverse()
                for reg in regs:
                    self.view.insert(edit, reg.begin(), '\n')

            if save:
                output.write_line("Saving...")
                # Otherwise the "dirty" indicator is not removed
                sublime.set_timeout(lambda: self.view.run_command("save"), 20)

            # Finish
            set_viewport(self.view, vp)
            output.write("[Finished in %.3fs]" %
                         (time.time() - self.start_time))