Esempio n. 1
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):
            self.output = output
        else:
            self.output = OutputPanel.create(self.window,
                                             self.output_panel_name)

        output_settings = self.output.view.settings()
        output_settings.set('result_file_regex', self.file_regex)
        output_settings.set('result_base_dir', path)
    def _test_syntaxes(self, *, name, configuration, tests, exclude=[]):
        test_working_path = TESTS_PATH / name
        test_working_path.file_path().mkdir(parents=True)

        output = OutputPanel.create(sublime.active_window(), 'YAMLMacros')

        syntax_path = test_working_path / (name + '.sublime-syntax')
        build_configuration(name, configuration, syntax_path.file_path(),
                            output)

        sublime.run_command(
            'build_js_custom_tests', {
                'syntax_path': str(syntax_path),
                'suites': tests,
                'exclude': exclude,
                'destination_directory': str(test_working_path.file_path()),
            })

        yield syntax_path.exists
        yield SYNTAX_DELAY  # Hope this gives Sublime long enough to compile it.

        all_failures = []

        for test_dest in test_working_path.glob('syntax_test*'):
            assertion_count, failures = sublime_api.run_syntax_test(
                str(test_dest))

            if failures and failures[0].endswith(
                    'does not match scope [text.plain]'):
                raise RuntimeError(
                    'Sublime did not compile {!s} in time.'.format(test_dest))
            else:
                all_failures.extend(failures)

        self.assertEqual(all_failures, [])
Esempio n. 3
0
 def __init__(self, window=None, output=None):
     if isinstance(output, OutputPanel):
         self.output = output
     elif window:
         self.output = OutputPanel.create(
             window, self.output_panel_name,
             read_only=True, force_writes=True
         )
Esempio n. 4
0
    def test_unlisted(self):
        self.panel = OutputPanel.create(self.window,
                                        self.panel_name,
                                        unlisted=True)

        self.panel.show()
        self.assertTrue(self.panel.is_visible())
        self.assertNotIn(self.panel.full_name, self.window.panels())
    def run(self, name: str, configuration: dict,
            destination_path: str) -> None:
        logger.info('Directly building to {}…'.format(destination_path))

        output = OutputPanel.create(self.window, 'YAMLMacros')
        # output.show()

        build_configuration(name, configuration, destination_path, output)
 def __init__(self, window=None, output=None):
     if isinstance(output, OutputPanel):
         self.output = output
     elif window:
         self.output = OutputPanel.create(
             window, self.output_panel_name,
             read_only=True, force_writes=True
         )
Esempio n. 7
0
    def test_settings(self):
        self.panel = OutputPanel.create(
            self.window,
            self.panel_name,
            settings={"test_setting": "Hello, World!"})

        view_settings = self.panel.view.settings()
        self.assertEqual(view_settings.get("test_setting"), "Hello, World!")
Esempio n. 8
0
    def test_attach(self):
        self.panel = OutputPanel.create(self.window,
                                        self.panel_name,
                                        unlisted=True)

        other = OutputPanel(self.window, self.panel_name)
        self.assertEqual(self.panel.view.id(), other.view.id())

        self.panel.destroy()
        self.assertRaises(ValueError, other.tell)
    def run(self, versions: 'Optional[List[str]]' = None) -> None:
        output = OutputPanel.create(self.window, 'YAMLMacros')
        output.show()

        settings = get_settings()
        configurations = get_configurations(settings)

        to_delete = {
            syntax_path.stem: syntax_path
            for syntax_path in SYNTAXES_BUILD_PATH.glob('*.sublime-syntax')
            if syntax_path.stem not in configurations
        }
        to_build = configurations

        if versions is not None:
            not_none_versions = versions

            def filter_by_versions(d: dict) -> dict:
                return {k: v for k, v in d.items() if k in not_none_versions}

            to_delete = filter_by_versions(to_delete)
            to_build = filter_by_versions(to_build)

        try:
            SYNTAXES_BUILD_PATH.file_path().mkdir(parents=True)
        except FileExistsError:
            pass

        if settings.get('reassign_when_deleting', False):
            replacement = settings['reassign_when_deleting']
            assert isinstance(replacement, str)
            if replacement.startswith('scope:'):
                replacement = get_syntax_for_scope(replacement[6:])

            paths_to_delete = [str(path) for path in to_delete.values()]

            sublime.run_command('reassign_syntaxes', {
                'syntaxes': paths_to_delete,
                'replacement': replacement,
            })

        def run() -> None:
            for name, syntax_path in to_delete.items():
                logger.info('Deleting configuration {}…'.format(name))
                syntax_path.file_path().unlink()

            for name, configuration in to_build.items():
                logger.info('Building configuration {}…'.format(name))
                destination_path = (SYNTAXES_BUILD_PATH /
                                    (name + '.sublime-syntax')).file_path()
                build_configuration(name, configuration, str(destination_path),
                                    output)

        Thread(target=run).start()
Esempio n. 10
0
    def test_stream_operations(self):
        self.panel = OutputPanel.create(self.window, self.panel_name)

        self.panel.write("Hello, ")
        self.panel.print("World!")

        self.panel.seek_start()
        self.panel.print("Top")

        self.panel.seek_end()
        self.panel.print("Bottom")

        self.panel.seek(4)
        self.panel.print("After Top")

        self.assertContents("Top\nAfter Top\nHello, World!\nBottom\n")
Esempio n. 11
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):
            self.output = output
        else:
            self.output = OutputPanel.create(
                self.window,
                self.output_panel_name
            )

        output_settings = self.output.view.settings()
        output_settings.set('result_file_regex', self.file_regex)
        output_settings.set('result_base_dir', path)
Esempio n. 12
0
    def run(self, versions=None):
        output = OutputPanel.create(self.window, 'YAMLMacros')
        output.show()

        configurations = get_configurations(get_settings())

        for syntax_path in SYNTAXES_BUILD_PATH.glob('*.sublime-syntax'):
            if syntax_path.stem not in configurations:
                syntax_path.file_path().unlink()

        try:
            SYNTAXES_BUILD_PATH.file_path().mkdir(parents=True)
        except FileExistsError:
            pass

        if versions:
            configurations = {
                name: configurations[name]
                for name in versions
            }

        build_configurations(configurations, SYNTAXES_BUILD_PATH, output)
Esempio n. 13
0
    def test_show_hide(self):
        self.panel = OutputPanel.create(self.window, self.panel_name)

        self.panel.show()

        self.assertTrue(self.panel.is_visible())
        self.assertEqual(self.window.active_panel(), self.panel.full_name)

        self.panel.hide()

        self.assertFalse(self.panel.is_visible())
        self.assertNotEqual(self.window.active_panel(), self.panel.full_name)

        self.panel.toggle_visibility()

        self.assertTrue(self.panel.is_visible())
        self.assertEqual(self.window.active_panel(), self.panel.full_name)

        self.panel.toggle_visibility()

        self.assertFalse(self.panel.is_visible())
        self.assertNotEqual(self.window.active_panel(), self.panel.full_name)
Esempio n. 14
0
    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('packagedev_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.create(
            self.view.window() or sublime.active_window(), "package_dev",
            read_only=True, force_writes=True
        ) as output:
            output.show()
            if _output_text:
                output.print(_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 Exception:
                output.print("Unexpected error occurred while parsing, "
                             "please see the console for details.")
                raise

            if not data:
                output.print("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 Exception:
                output.print("Unexpected error occurred while dumping, "
                             "please see the console for details.")
                raise

            if not text:
                output.print("Error re-dumping the data in file (no output).")
                status("Error re-dumping the data (no output).", True)
                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.print("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.print("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))
Esempio n. 15
0
 def test_destroy(self):
     self.panel = OutputPanel.create(self.window, self.panel_name)
     self.panel.destroy()
     self.assertIsNone(self.window.find_output_panel(self.panel.name))
Esempio n. 16
0
 def test_exists(self):
     self.panel = OutputPanel.create(self.window, self.panel_name)
     self.assertIsNotNone(self.window.find_output_panel(self.panel.name))
Esempio n. 17
0
    def test_clear(self):
        self.panel = OutputPanel.create(self.window, self.panel_name)

        self.panel.write("Some text")
        self.panel.clear()
        self.assertContents("")
Esempio n. 18
0
    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, ...)

        window = self.view.window()

        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('packagedev_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.create(
            self.view.window() or sublime.active_window(), "package_dev",
            read_only=True, force_writes=True
        ) as output:
            output.show()
            if _output_text:
                output.print(_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 Exception:
                output.print("Unexpected error occurred while parsing, "
                             "please see the console for details.")
                raise

            if not data:
                output.print("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 Exception:
                output.print("Unexpected error occurred while dumping, "
                             "please see the console for details.")
                raise

            if not text:
                output.print("Error re-dumping the data in file (no output).")
                status("Error re-dumping the data (no output).", window, True)
                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.print("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.print("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))
Esempio n. 19
0
    def run(self, source_format=None, target_format=None, ext=None,
            open_new_file=False, rearrange_yaml_syntax_def=False, _output=None, **kwargs):
        """Available parameters:

        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.

        **kwargs
            Will be forwarded to both the loading function and the dumping function, after
            stripping unsupported 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`.
        """
        self.view = self.window.active_view()

        # 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)
        file_path = Path(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("Loader for '%s' not supported/implemented." % source_format)

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

        # Now the actual "building" starts (collecting remaining parameters)
        with OutputPanel.create(self.window, "package_dev",
                                read_only=True, force_writes=True) 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.print(' %s\n' % Loader.name)
                        break

                if not source_format:
                    return output.print("\nUnable to detect file type.")
                elif target_format == source_format:
                    return output.print("File already is %s." % Loader.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)

            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'],
                                            file_path.stem + get_new_ext(target_format_)])
                            items.append(itm)

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

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

                        kwargs.update(target['kwargs'])
                        kwargs.update(dict(source_format=source_format, _output=output))
                        self.run(**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.print("\nTarget and source file format are identical. (%s)"
                                        % target_format)

                if target_format not in dumpers.get:
                    return output.print("\nDumper for '%s' not supported/implemented."
                                        % target_format)

                output.print(' %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(**kwargs)
            except Exception:
                output.print("Unexpected error occurred while parsing, "
                             "please see the console for details.")
                raise
            if not data:
                return

            # Determine new file name
            new_file_path = file_path.with_suffix(get_new_ext(target_format))
            new_dir = new_file_path.parent
            try:
                os.makedirs(str(new_dir), exist_ok=True)
            except OSError:
                output.print("Could not create folder '%s'" % new_dir)
                return

            # Now dump to new file
            dumper = dumpers.get[target_format](self.window, self.view, str(new_file_path),
                                                output=output)
            try:
                dumper.dump(data, **kwargs)
            except Exception:
                output.print("Unexpected error occurred while dumping, "
                             "please see the console for details.")
                raise
            self.status("File conversion successful. (%s -> %s)"
                        % (source_format, target_format))

            # Finish
            output.print("[Finished in %.3fs]" % (time.time() - start_time))
            # We need to save the text if calling "rearrange_yaml_syntax_def"
            # because `get_output_panel` resets its contents.
            output_text = get_text(output.view)

        # Continue with potential further steps
        if open_new_file or rearrange_yaml_syntax_def:
            new_view = self.window.open_file(str(new_file_path))

            if rearrange_yaml_syntax_def:
                new_view.run_command('packagedev_rearrange_yaml_syntax_def',
                                     {'save': True, '_output_text': output_text})
Esempio n. 20
0
    def run(self,
            source_format=None,
            target_format=None,
            ext=None,
            open_new_file=False,
            rearrange_yaml_syntax_def=False,
            _output=None,
            **kwargs):
        """Available parameters:

        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.

        **kwargs
            Will be forwarded to both the loading function and the dumping function, after
            stripping unsupported 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`.
        """
        self.view = self.window.active_view()

        # 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)
        file_path = Path(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("Loader for '%s' not supported/implemented." %
                               source_format)

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

        # Now the actual "building" starts (collecting remaining parameters)
        with OutputPanel.create(self.window,
                                "package_dev",
                                read_only=True,
                                force_writes=True) 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.print(' %s\n' % Loader.name)
                        break

                if not source_format:
                    return output.print("\nUnable to detect file type.")
                elif target_format == source_format:
                    return output.print("File already is %s." % Loader.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)

            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'],
                                file_path.stem + get_new_ext(target_format_)
                            ])
                            items.append(itm)

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

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

                        kwargs.update(target['kwargs'])
                        kwargs.update(
                            dict(source_format=source_format, _output=output))
                        self.run(**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.print(
                        "\nTarget and source file format are identical. (%s)" %
                        target_format)

                if target_format not in dumpers.get:
                    return output.print(
                        "\nDumper for '%s' not supported/implemented." %
                        target_format)

                output.print(' %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(**kwargs)
            except Exception:
                output.print("Unexpected error occurred while parsing, "
                             "please see the console for details.")
                raise
            if not data:
                return

            # Determine new file name
            new_file_path = file_path.with_suffix(get_new_ext(target_format))
            new_dir = new_file_path.parent
            try:
                os.makedirs(str(new_dir), exist_ok=True)
            except OSError:
                output.print("Could not create folder '%s'" % new_dir)
                return

            # Now dump to new file
            dumper = dumpers.get[target_format](self.window,
                                                self.view,
                                                str(new_file_path),
                                                output=output)
            try:
                dumper.dump(data, **kwargs)
            except Exception:
                output.print("Unexpected error occurred while dumping, "
                             "please see the console for details.")
                raise
            self.status("File conversion successful. (%s -> %s)" %
                        (source_format, target_format))

            # Finish
            output.print("[Finished in %.3fs]" % (time.time() - start_time))
            # We need to save the text if calling "rearrange_yaml_syntax_def"
            # because `get_output_panel` resets its contents.
            output_text = get_text(output.view)

        # Continue with potential further steps
        if open_new_file or rearrange_yaml_syntax_def:
            new_view = self.window.open_file(str(new_file_path))

            if rearrange_yaml_syntax_def:
                new_view.run_command('packagedev_rearrange_yaml_syntax_def', {
                    'save': True,
                    '_output_text': output_text
                })