1# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
2#
3# Copyright (c) 2008 - 2015 by Wilbert Berendsen
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 2
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18# See http://www.gnu.org/licenses/ for more information.
19
20"""
21LilyPond DOM
22
23(c) 2008-2011 Wilbert Berendsen
24License: GPL.
25
26A simple Document Object Model for LilyPond documents.
27
28The purpose is to easily build a LilyPond document with good syntax,
29not to fully understand all features LilyPond supports. (This DOM does
30not enforce a legal LilyPond file.)
31
32All elements of a LilyPond document inherit Node.
33
34Note: elements keep a weak reference to their parent.
35
36"""
37
38from __future__ import unicode_literals
39from __future__ import absolute_import # prevent picking old stale node.py from package
40
41try:
42    string_types = basestring
43except NameError:
44    string_types = str
45
46import fractions
47import re
48
49import ly.pitch
50import ly.duration
51from ly.node import WeakNode
52
53
54class LyNode(WeakNode):
55    """
56    Base class for LilyPond objects, based on Node,
57    which takes care of the tree structure.
58    """
59
60    ##
61    # True if this element is single LilyPond atom, word, note, etc.
62    # When it is the only element inside { }, the brackets can be removed.
63    isAtom = False
64
65    ##
66    # The number of newlines this object wants before it.
67    before = 0
68
69    ##
70    # The number of newlines this object wants after it.
71    after = 0
72
73    def ly(self, printer):
74        """
75        Returns printable output for this object.
76        Can ask printer for certain settings, e.g. pitch language etc.
77        """
78        return ''
79
80    def concat(self, other):
81        """
82        Returns a string with newlines to concat this node to another one.
83        If zero newlines are requested, an empty string is returned.
84        """
85        return '\n' * max(self.after, other.before)
86
87
88##
89# Leaf and Container are the two base classes the rest of the LilyPond
90# element classes is based on.
91#
92class Leaf(LyNode):
93    """ A leaf node without children """
94    pass
95
96
97class Container(LyNode):
98    """ A node that concatenates its children on output """
99
100    ##
101    # default character to concatenate children with
102    defaultSpace = " "
103
104    @property
105    def before(self):
106        if len(self):
107            return self[0].before
108        else:
109            return 0
110
111    @property
112    def after(self):
113        if len(self):
114            return self[-1].after
115        else:
116            return 0
117
118    def ly(self, printer):
119        if len(self) == 0:
120            return ''
121        else:
122            n = self[0]
123            res = [n.ly(printer)]
124            for m in self[1:]:
125                res.append(n.concat(m) or self.defaultSpace)
126                res.append(m.ly(printer))
127                n = m
128            return "".join(res)
129
130
131##
132# Helper classes
133#
134class Printer(object):
135    """
136    Performs certain operations on behalf of a LyNode tree,
137    like quoting strings or translating pitch names, etc.
138    """
139
140    # You may change these
141    primary_quote_left = '\u2018'
142    primary_quote_right = '\u2019'
143    secondary_quote_left = '\u201C'
144    secondary_quote_right = '\u201D'
145
146    def __init__(self):
147        self.typographicalQuotes = True
148        self.language = "nederlands"
149        self.indentString = '  '
150
151    def quoteString(self, text):
152        if self.typographicalQuotes:
153            text = re.sub(r'"(.*?)"', self.primary_quote_left + r'\1' + self.primary_quote_right, text)
154            text = re.sub(r"'(.*?)'", self.secondary_quote_left + r'\1' + self.secondary_quote_right, text)
155            text = text.replace("'", '\u2018')
156        # escape regular double quotes
157        text = text.replace('"', '\\"')
158        # quote the string
159        return '"{0}"'.format(text)
160
161    def indentGen(self, node, startIndent = 0):
162        """
163        A generator that walks on the output of the given node,
164        and returns properly indented LilyPond code.
165        """
166        d = startIndent
167        for t in node.ly(self).splitlines() + [''] * node.after:
168            if d and re.match(r'#?}|>|%}', t):
169                d -= 1
170            yield self.indentString * d + t
171            if re.search(r'(\{|<|%{)$', t):
172                d += 1
173
174    def indent(self, node):
175        """
176        Return a formatted printout of node (and its children)
177        """
178        return '\n'.join(self.indentGen(node))
179
180
181class Reference(object):
182    """
183    A simple object that keeps a name, to use as a (context)
184    identifier. Set the name attribute to the name you want
185    to display, and on all places in the document the name
186    will show up.
187    """
188    def __init__(self, name=""):
189        self.name = name
190
191    def __format__(self, format_spec):
192        return self.name
193
194
195class Named(object):
196    r"""
197    Mixin to print a \name before the contents of the container.
198    format() is called on the self.name attribute, so it may also
199    be a Reference.
200    """
201    name = ""
202
203    def ly(self, printer):
204        return "\\{0} {1}".format(self.name, super(Named, self).ly(printer))
205
206
207class HandleVars(object):
208    """
209    A powerful mixin class to facilitate handling unique variable assignments
210    inside a Container more.
211    E.g.:
212    >>> h = Header()
213    >>> h['composer'] = "Johann Sebastian Bach"
214    creates a subnode (by default Assignment) with the name 'composer', and
215    that node again gets an autogenerated subnode of type QuotedString (if the
216    argument wasn't already a Node).
217    """
218    childClass = None # To be filled in later
219
220    def ifbasestring(func):
221        """
222        Ensure that the method is only called for basestring objects.
223        Otherwise the same method from the super class is called.
224        """
225        def newfunc(obj, name, *args):
226            if isinstance(name, string_types):
227                return func(obj, name, *args)
228            else:
229                f = getattr(super(HandleVars, obj), func.__name__)
230                return f(name, *args)
231        return newfunc
232
233    @ifbasestring
234    def __getitem__(self, name):
235        for node in self.find_children(self.childClass, 1):
236            if node.name == name:
237                return node
238
239    @ifbasestring
240    def __setitem__(self, name, valueObj):
241        if not isinstance(valueObj, LyNode):
242            valueObj = self.importNode(valueObj)
243        assignment = self[name]
244        if assignment:
245            assignment.setValue(valueObj)
246        else:
247            self.childClass(name, self, valueObj)
248
249    @ifbasestring
250    def __contains__(self, name):
251        return bool(self[name])
252
253    @ifbasestring
254    def __delitem__(self, name):
255        h = self[name]
256        if h:
257            self.remove(h)
258
259    def importNode(self, obj):
260        """
261        Try to interpret the object and transform it into a Node object
262        of the right species.
263        """
264        return QuotedString(obj)
265
266
267class AddDuration(object):
268    """ Mixin to add a duration (as child). """
269    def ly(self, printer):
270        s = super(AddDuration, self).ly(printer)
271        dur = self.find_child(Duration, 1)
272        if dur:
273            s += dur.ly(printer)
274        return s
275
276
277class Block(Container):
278    """
279    A vertical container type that puts everything on a new line.
280    """
281    defaultSpace = "\n"
282    before, after = 1, 1
283
284
285class Document(Container):
286    """
287    A container type that puts everything on a new line.
288    To be used as a full LilyPond document.
289    """
290    defaultSpace = "\n"
291    after = 1
292
293
294##
295# These classes correspond to real LilyPond data.
296#
297class Text(Leaf):
298    """ A leaf node with arbitrary text """
299    def __init__(self, text="", parent=None):
300        super(Text, self).__init__(parent)
301        self.text = text
302
303    def ly(self, printer):
304        return self.text
305
306
307class TextDur(AddDuration, Text):
308    """ A text note with an optional duration as child. """
309    pass
310
311
312class Line(Text):
313    """ A text node that claims its own line. """
314    before, after = 1, 1
315
316
317class Comment(Text):
318    """ A LilyPond comment at the end of a line """
319    after = 1
320
321    def ly(self, printer):
322        return re.compile('^', re.M).sub('% ', self.text)
323
324
325class LineComment(Comment):
326    """ A LilyPond comment that takes a full line """
327    before = 1
328
329
330class BlockComment(Comment):
331    """ A block comment between %{ and %} """
332    @property
333    def before(self):
334        return '\n' in self.text and 1 or 0
335
336    @property
337    def after(self):
338        return '\n' in self.text and 1 or 0
339
340    def ly(self, printer):
341        text = self.text.replace('%}', '')
342        f = "%{{\n{0}\n%}}" if '\n' in text else "%{{ {0} %}}"
343        return f.format(text)
344
345
346class QuotedString(Text):
347    """ A string that is output inside double quotes. """
348    isAtom = True
349    def ly(self, printer):
350        # we call format(), since self.text MIGHT be a Reference...
351        return printer.quoteString(format(self.text))
352
353
354class Newline(LyNode):
355    """ A newline. """
356    after = 1
357
358
359class BlankLine(Newline):
360    """ A blank line. """
361    before = 1
362
363
364class Scheme(Text):
365    """ A Scheme expression, without the extra # prepended """
366    isAtom = True
367
368    def ly(self, printer):
369        return '#' + self.text
370
371
372class Version(Line):
373    """ a LilyPond version instruction """
374    def ly(self, printer):
375        return r'\version "{0}"'.format(self.text)
376
377
378class Include(Line):
379    r""" a LilyPond \include statement """
380    def ly(self, printer):
381        return r'\include "{0}"'.format(self.text)
382
383
384class Assignment(Container):
385    """
386    A varname = value construct with it's value as its first child
387    The name can be a string or a Reference object: so that everywhere
388    where this varname is referenced, the name is the same.
389    """
390    before, after = 1, 1
391
392    def __init__(self, name=None, parent=None, valueObj=None):
393        super(Assignment, self).__init__(parent)
394        self.name = name
395        if valueObj:
396            self.append(valueObj)
397
398    # Convenience methods:
399    def setValue(self, obj):
400        if len(self):
401            self[0] = obj
402        else:
403            self.append(obj)
404
405    def value(self):
406        if len(self):
407            return self[0]
408
409    def ly(self, printer):
410        return "{0} = {1}".format(self.name, super(Assignment, self).ly(printer))
411
412
413HandleVars.childClass = Assignment
414
415
416class Identifier(Leaf):
417    """
418    An identifier, prints as \\name.
419    Name may be a string or a Reference object.
420    """
421    isAtom = True
422
423    def __init__(self, name=None, parent=None):
424        super(Identifier, self).__init__(parent)
425        self.name = name
426
427    def ly(self, printer):
428        return "\\{0}".format(self.name)
429
430
431class Statement(Named, Container):
432    """
433    Base class for statements with arguments. The statement is read in the
434    name attribute, the arguments are the children.
435    """
436    before = 0 # do not read property from container
437    isAtom = True
438
439
440class Command(Statement):
441    """
442    Use this to create a LilyPond command supplying the name (or a Reference)
443    when instantiating.
444    """
445    def __init__(self, name, parent=None):
446        super(Command, self).__init__(parent)
447        self.name = name
448
449
450class Enclosed(Container):
451    """
452    Encloses all children between brackets: { ... }
453    If may_remove_brackets is True in subclasses, the brackets are
454    removed if there is only one child and that child is an atom (i.e.
455    a single LilyPond expression.
456    """
457    may_remove_brackets = False
458    pre, post = "{", "}"
459    before, after = 0, 0
460    isAtom = True
461
462    def ly(self, printer):
463        if len(self) == 0:
464            return " ".join((self.pre, self.post))
465        sup = super(Enclosed, self)
466        text = sup.ly(printer)
467        if self.may_remove_brackets and len(self) == 1 and self[0].isAtom:
468            return text
469        elif sup.before or sup.after or '\n' in text:
470            return "".join((self.pre, "\n" * max(sup.before, 1), text,
471                                      "\n" * max(sup.after, 1), self.post))
472        else:
473            return " ".join((self.pre, text, self.post))
474
475
476class Seq(Enclosed):
477    """ An SequentialMusic expression between { } """
478    pre, post = "{", "}"
479
480
481class Sim(Enclosed):
482    """ An SimultaneousMusic expression between << >> """
483    pre, post = "<<", ">>"
484
485
486class Seqr(Seq): may_remove_brackets = True
487class Simr(Sim): may_remove_brackets = True
488
489
490class SchemeLily(Enclosed):
491    """ A LilyPond expression between #{ #} (inside scheme) """
492    pre, post = "#{", "#}"
493
494
495class SchemeList(Enclosed):
496    """ A list of items enclosed in parentheses """
497    pre, post = "(", ")"
498
499    def ly(self, printer):
500        return self.pre + Container.ly(self, printer) + self.post
501
502
503class StatementEnclosed(Named, Enclosed):
504    """
505    Base class for LilyPond commands that have a single bracket-enclosed
506    list of arguments.
507    """
508    may_remove_brackets = True
509
510
511class CommandEnclosed(StatementEnclosed):
512    """
513    Use this to print LilyPond commands that have a single
514    bracket-enclosed list of arguments. The command name is supplied to
515    the constructor.
516    """
517    def __init__(self, name, parent=None):
518        super(CommandEnclosed, self).__init__(parent)
519        self.name = name
520
521
522class Section(StatementEnclosed):
523    """
524    Very much like a Statement. Use as base class for \\book { }, \\score { }
525    etc. By default never removes the brackets and always starts on a new line.
526    """
527    may_remove_brackets = False
528    before, after = 1, 1
529
530
531class Book(Section): name = 'book'
532class BookPart(Section): name = 'bookpart'
533class Score(Section): name = 'score'
534class Paper(HandleVars, Section): name = 'paper'
535class Layout(HandleVars, Section): name = 'layout'
536class Midi(HandleVars, Section): name = 'midi'
537class Header(HandleVars, Section): name = 'header'
538
539
540class With(HandleVars, Section):
541    """ If this item has no children, it prints nothing. """
542    name = 'with'
543    before, after = 0, 0
544
545    def ly(self, printer):
546        if len(self):
547            return super(With, self).ly(printer)
548        else:
549            return ''
550
551
552class ContextName(Text):
553    """
554    Used to print a context name, like \\Score.
555    """
556    def ly(self, printer):
557        return "\\" + self.text
558
559
560class Context(HandleVars, Section):
561    r"""
562    A \context section for use inside Layout or Midi sections.
563    """
564    name = 'context'
565
566    def __init__(self, contextName="", parent=None):
567        super(Context, self).__init__(parent)
568        if contextName:
569            ContextName(contextName, self)
570
571
572class ContextType(Container):
573    r"""
574    \new or \context Staff = 'bla' \with { } << music >>
575
576    A \with (With) element is added automatically as the first child as soon
577    as you use our convenience methods that manipulate the variables
578    in \with. If the \with element is empty, it does not print anything.
579    You should add one other music object to this.
580    """
581    before, after = 1, 1
582    isAtom = True
583    ctype = None
584
585    def __init__(self, cid=None, new=True, parent=None):
586        super(ContextType, self).__init__(parent)
587        self.new = new
588        self.cid = cid
589
590    def ly(self, printer):
591        res = []
592        res.append(self.new and "\\new" or "\\context")
593        res.append(self.ctype or self.__class__.__name__)
594        if self.cid:
595            res.append("=")
596            res.append(printer.quoteString(format(self.cid)))
597        res.append(super(ContextType, self).ly(printer))
598        return " ".join(res)
599
600    def getWith(self):
601        """
602        Gets the attached with clause. Creates it if not there.
603        """
604        for node in self:
605            if isinstance(node, With):
606                return node
607        self.insert(0, With())
608        return self[0]
609
610    def addInstrumentNameEngraverIfNecessary(self):
611        """
612        Adds the Instrument_name_engraver to the node if it would need it
613        to print instrument names.
614        """
615        if not isinstance(self,
616            (Staff, RhythmicStaff, PianoStaff, Lyrics, FretBoards)):
617            Line('\\consists "Instrument_name_engraver"', self.getWith())
618
619
620class ChoirStaff(ContextType): pass
621class ChordNames(ContextType): pass
622class CueVoice(ContextType): pass
623class Devnull(ContextType): pass
624class DrumStaff(ContextType): pass
625class DrumVoice(ContextType): pass
626class Dynamics(ContextType): pass
627class FiguredBass(ContextType): pass
628class FretBoards(ContextType): pass
629class Global(ContextType): pass
630class GrandStaff(ContextType): pass
631class GregorianTranscriptionStaff(ContextType): pass
632class GregorianTranscriptionVoice(ContextType): pass
633class InnerChoirStaff(ContextType): pass
634class InnerStaffGroup(ContextType): pass
635class Lyrics(ContextType): pass
636class MensuralStaff(ContextType): pass
637class MensuralVoice(ContextType): pass
638class NoteNames(ContextType): pass
639class PianoStaff(ContextType): pass
640class RhythmicStaff(ContextType): pass
641class ScoreContext(ContextType):
642    r"""
643    Represents the Score context in LilyPond, but the name would
644    collide with the Score class that represents \score { } constructs.
645
646    Because the latter is used more often, use ScoreContext to get
647    \new Score etc.
648    """
649    ctype = 'Score'
650
651class Staff(ContextType): pass
652class StaffGroup(ContextType): pass
653class TabStaff(ContextType): pass
654class TabVoice(ContextType): pass
655class VaticanaStaff(ContextType): pass
656class VaticanaVoice(ContextType): pass
657class Voice(ContextType): pass
658
659
660class UserContext(ContextType):
661    r"""
662    Represents a context the user creates.
663    e.g. \new MyStaff = cid << music >>
664    """
665    def __init__(self, ctype, cid=None, new=True, parent=None):
666        super(UserContext, self).__init__(cid, new, parent)
667        self.ctype = ctype
668
669
670class ContextProperty(Leaf):
671    """
672    A Context.property or Context.layoutObject construct.
673    Call e.g. ContextProperty('aDueText', 'Staff') to get 'Staff.aDueText'.
674    """
675    def __init__(self, prop, context=None, parent=None):
676        self.prop = prop
677        self.context = context
678
679    def ly(self, printer):
680        if self.context:
681            # In \lyrics or \lyricmode: put spaces around dot.
682            p = self.find_parent(InputMode)
683            if p and isinstance(p, LyricMode):
684                f = '{0} . {1}'
685            else:
686                f = '{0}.{1}'
687            return f.format(self.context, self.prop)
688        else:
689            return self.prop
690
691
692class InputMode(StatementEnclosed):
693    """
694    The abstract base class for input modes such as lyricmode/lyrics,
695    chordmode/chords etc.
696    """
697    pass
698
699
700class ChordMode(InputMode): name = 'chordmode'
701class InputChords(ChordMode): name = 'chords'
702class LyricMode(InputMode): name = 'lyricmode'
703class InputLyrics(LyricMode): name = 'lyrics'
704class NoteMode(InputMode): name = 'notemode'
705class InputNotes(NoteMode): name = 'notes'
706class FigureMode(InputMode): name = 'figuremode'
707class InputFigures(FigureMode): name = 'figures'
708class DrumMode(InputMode): name = 'drummode'
709class InputDrums(DrumMode): name = 'drums'
710
711
712class AddLyrics(InputLyrics):
713    name = 'addlyrics'
714    may_remove_brackets = False
715    before, after = 1, 1
716
717
718class LyricsTo(LyricMode):
719    name = 'lyricsto'
720
721    def __init__(self, cid, parent=None):
722        super(LyricsTo, self).__init__(parent)
723        self.cid = cid
724
725    def ly(self, printer):
726        res = ["\\" + self.name]
727        res.append(printer.quoteString(format(self.cid)))
728        res.append(Enclosed.ly(self, printer))
729        return " ".join(res)
730
731
732class Pitch(Leaf):
733    """
734    A pitch with octave, note, alter.
735    octave is specified by an integer, zero for the octave containing middle C.
736    note is a number from 0 to 6, with 0 corresponding to pitch C and 6
737    corresponding to pitch B.
738    alter is the number of whole tones for alteration (can be int or Fraction)
739    """
740
741    def __init__(self, octave=0, note=0, alter=0, parent=None):
742        super(Pitch, self).__init__(parent)
743        self.octave = octave
744        self.note = note
745        self.alter = fractions.Fraction(alter)
746
747    def ly(self, printer):
748        """
749        Print the pitch in the preferred language.
750        """
751        p = ly.pitch.pitchWriter(printer.language)(self.note, self.alter)
752        if self.octave < -1:
753            return p + ',' * (-self.octave - 1)
754        elif self.octave > -1:
755            return p + "'" * (self.octave + 1)
756        return p
757
758
759class Duration(Leaf):
760    r"""
761    A duration with duration (in logarithmic form): (-2 ... 8),
762    where -2 = \longa, -1 = \breve, 0 = 1, 1 = 2, 2 = 4, 3 = 8, 4 = 16, etc,
763    dots (number of dots),
764    factor (Fraction giving the scaling of the duration).
765    """
766    def __init__(self, dur, dots=0, factor=1, parent=None):
767        super(Duration, self).__init__(parent)
768        self.dur = dur # log
769        self.dots = dots
770        self.factor = fractions.Fraction(factor)
771
772    def ly(self, printer):
773        return ly.duration.tostring(self.dur, self.dots, self.factor)
774
775
776class Chord(Container):
777    """
778    A chord containing one of more Pitches and optionally one Duration.
779    This is a bit of a hack, awaiting real music object support.
780    """
781    def ly(self, printer):
782        pitches = list(self.find_children(Pitch, 1))
783        if len(pitches) == 1:
784            s = pitches[0].ly(printer)
785        else:
786            s = "<{0}>".format(' '.join(p.ly(printer) for p in pitches))
787        duration = self.find_child(Duration, 1)
788        if duration:
789            s += duration.ly(printer)
790        return s
791
792
793class Relative(Statement):
794    r"""
795    \relative <pitch> music
796
797    You should add a Pitch (optionally) and another music object,
798    e.g. Sim or Seq, etc.
799    """
800    name = 'relative'
801
802
803class Transposition(Statement):
804    r"""
805    \transposition <pitch>
806    You should add a Pitch.
807    """
808    name = 'transposition'
809
810
811class KeySignature(Leaf):
812    r"""
813    A key signature expression, like:
814
815    \key c \major
816    The pitch should be given in the arguments note and alter and is written
817    out in the document's language.
818    """
819    def __init__(self, note=0, alter=0, mode="major", parent=None):
820        super(KeySignature, self).__init__(parent)
821        self.note = note
822        self.alter = fractions.Fraction(alter)
823        self.mode = mode
824
825    def ly(self, printer):
826        pitch = ly.pitch.pitchWriter(printer.language)(self.note, self.alter)
827        return "\\key {0} \\{1}".format(pitch, self.mode)
828
829
830class TimeSignature(Leaf):
831    r"""
832    A time signature, like: \time 4/4
833    """
834    def __init__(self, num, beat, parent=None):
835        super(TimeSignature, self).__init__(parent)
836        self.num = num
837        self.beat = beat
838
839    def ly(self, printer):
840        return "\\time {0}/{1}".format(self.num, self.beat)
841
842
843class Partial(Named, Duration):
844    r"""
845    \partial <duration>
846    You should add a Duration element
847    """
848    name = "partial"
849    before, after = 1, 1
850
851
852class Tempo(Container):
853    r"""
854    A tempo setting, like: \tempo 4 = 100
855    May have a child markup or quoted string.
856    """
857    before, after = 1, 1
858
859    def __init__(self, duration, value, parent=None):
860        super(Tempo, self).__init__(parent)
861        self.duration = duration
862        self.value = value
863
864    def ly(self, printer):
865        result = ['\\tempo']
866        if len(self) > 0:
867            result.append(super(Tempo, self).ly(printer))
868        if self.value:
869            result.append("{0}={1}".format(self.duration, self.value))
870        return ' '.join(result)
871
872
873class Clef(Leaf):
874    """
875    A clef.
876    """
877    def __init__(self, clef, parent=None):
878        super(Clef, self).__init__(parent)
879        self.clef = clef
880
881    def ly(self, printer):
882        clef = self.clef if self.clef.isalpha() else '"{0}"'.format(self.clef)
883        return "\\clef " + clef
884
885
886class VoiceSeparator(Leaf):
887    r"""
888    A Voice Separator: \\
889    """
890    def ly(self, printer):
891        return r'\\'
892
893
894class Mark(Statement):
895    r"""
896    The \mark command.
897    """
898    name = 'mark'
899
900
901class Markup(StatementEnclosed):
902    r"""
903    The \markup command.
904    You can add many children, in that case Markup automatically prints
905    { and } around them.
906    """
907    name = 'markup'
908
909
910class MarkupEnclosed(CommandEnclosed):
911    """
912    A markup that auto-encloses all its arguments, like 'italic', 'bold'
913    etc.  You must supply the name.
914    """
915    pass
916
917
918class MarkupCommand(Command):
919    """
920    A markup command with more or no arguments, that does not auto-enclose
921    its arguments. Useful for commands like note-by-number or hspace.
922
923    You must supply the name. Its arguments are its children.
924    If one argument can be a markup list, use a Enclosed() construct for that.
925    """
926    pass
927
928
929
930