Source code for ly.server.command

# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
#
# Copyright (c) 2014 - 2015 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
The commands that are available to the command line.
"""

from __future__ import unicode_literals

import re
import sys

import ly

known_commands = [
    'mode',
    'version',
    'language',
    'indent',
    'reformat',
    'translate',
    'transpose',
    'rel2abs',
    'abs2rel',
    'musicxml',
    'highlight'
]

class _command(object):
    """Base class for commands.

    If the __init__() fails with TypeError or ValueError, the command is
    considered invalid and an error message will be written to the console
    in the parse_command() function in main.py.

    By default, __init__() expects no arguments. If your command does accept
    arguments, they are provided in a single argument that you should parse
    yourself.

    """
    def __init__(self):
        pass

    def run(self, opts, data):
        pass


[docs]class set_variable(_command): """set a configuration variable to a value""" def __init__(self, arg): self.name, self.value = arg.split('=', 1)
[docs] def run(self, opts, data): opts.set_variable(self.name, self.value)
################################# ### Base classes for commands ### ################################# # The command classes have a run() method that serves as a common structure # for each command type. From within run() a function is called to perform # the actual, command-specific funcionality. # The run() method also takes care of updating the data object with the correct # nesting structure and metadata. class _info_command(_command): """ Base class for commands that retrieve some info about the document. The result is appended to the data['info'] array as a dict with 'command' and 'info' fields. """ def run(self, opts, data): import ly.docinfo info = ly.docinfo.DocInfo(data['doc']['content'].document) text = self.get_info(info) data['info'].append({ 'command': self.__class__.__name__, 'info': text or "" }) def get_info(self, info): """ Should return the desired information from the docinfo object. """ raise NotImplementedError() class _edit_command(_command): """ Base class for commands that modify the input. The modifications overwrite the content of data['doc']['content'], so a subsequent edit commands builds on the modified content. The command name is added to the data['doc']['commands'] dict so it is possible to retrace which commands have been applied to the final result. """ def run(self, opts, data): self.edit(opts, data['doc']['content']) data['doc']['commands'].append(self.__class__.__name__) def edit(self, opts, cursor): """Should edit the cursor in-place.""" raise NotImplementedError() class _export_command(_command): """ Base class for commands that convert the input to another format. For each command an entry will be appended to data['exports'] field of the 'data' dict, leaving data['doc'] untouched. Each entry in data['exports'] has a ['doc'] and a ['command'] field, allowing the client to identify the """ def run(self, opts, data): export = self.export(opts, data['doc']['content'], data['exports']) data['exports'].append({ 'command': self.__class__.__name__, 'doc': export }) def export(self, opts, cursor, exports): """Should return the converted document as string.""" raise NotImplementedError() ##################### ### Info commands ### #####################
[docs]class mode(_info_command): """retrieve mode from document"""
[docs] def get_info(self, info): return info.mode()
[docs]class version(_info_command): """retrieve version from document"""
[docs] def get_info(self, info): return info.version_string()
[docs]class language(_info_command): """retrieve language from document"""
[docs] def get_info(self, info): return info.language()
##################### ### Edit commands ### #####################
[docs]class indent(_edit_command): """run the indenter"""
[docs] def indenter(self, opts): """Get a ly.indent.Indenter initialized with our options.""" import ly.indent i = ly.indent.Indenter() i.indent_tabs = opts.indent_tabs i.indent_width = opts.indent_width return i
[docs] def edit(self, opts, cursor): self.indenter(opts).indent(cursor)
[docs]class reformat(indent): """reformat the document"""
[docs] def edit(self, opts, cursor): import ly.reformat ly.reformat.reformat(cursor, self.indenter(opts))
[docs]class translate(_edit_command): """translate pitch names""" def __init__(self, language): if language not in ly.pitch.pitchInfo: raise ValueError() self.language = language
[docs] def edit(self, opts, cursor): import ly.pitch.translate try: changed = ly.pitch.translate.translate(cursor, self.language, opts.default_language) except ly.pitch.PitchNameNotAvailable as pna: raise ValueError(format(pna)) if not changed: version = ly.docinfo.DocInfo(cursor.document).version() ly.pitch.translate.insert_language(cursor.document, self.language, version)
[docs]class transpose(_edit_command): """transpose music""" def __init__(self, arg): import re result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", arg): r = ly.pitch.pitchReader("nederlands")(pitch) if r: result.append(ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) self.from_pitch, self.to_pitch = result
[docs] def edit(self, opts, cursor): import ly.pitch.transpose transposer = ly.pitch.transpose.Transposer(self.from_pitch, self.to_pitch) try: ly.pitch.transpose.transpose(cursor, transposer, opts.default_language) except ly.pitch.PitchNameNotAvailable as pna: language = ly.docinfo.DocInfo(cursor.document).language() or opts.default_language raise ValueError(format(pna))
[docs]class rel2abs(_edit_command): """convert relative music to absolute"""
[docs] def edit(self, opts, cursor): import ly.pitch.rel2abs ly.pitch.rel2abs.rel2abs(cursor, opts.default_language)
[docs]class abs2rel(_edit_command): """convert absolute music to relative"""
[docs] def edit(self, opts, cursor): import ly.pitch.abs2rel ly.pitch.abs2rel.abs2rel(cursor, opts.default_language)
####################### ### Export commands ### #######################
[docs]class musicxml(_export_command): """convert source to MusicXML"""
[docs] def export(self, opts, cursor, exports): import ly.musicxml writer = ly.musicxml.writer() writer.parse_document(cursor.document) #TODO!!! # In Python3 this incorrectly escapes the \n characters, # but leaving out the str() conversion returns a Bytes object, # which will in turn trigger an "object is not JSON serializable" error return str(writer.musicxml().tostring())
[docs]class highlight(_export_command): """convert source to syntax colored HTML."""
[docs] def export(self, opts, cursor, exports): import ly.colorize w = ly.colorize.HtmlWriter() # set configuration options w.full_html = opts.full_html w.inline_style = opts.inline_style w.stylesheet_ref = opts.stylesheet w.number_lines = opts.number_lines w.title = cursor.document.filename w.encoding = opts.output_encoding or "utf-8" w.wrapper_tag = opts.wrapper_tag w.wrapper_attribute = opts.wrapper_attribute w.document_id = opts.document_id w.linenumbers_id = opts.linenumbers_id return w.html(cursor)