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)
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))
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:
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, 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)
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)
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, 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)
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)