# GNU Solfege - free ear training software
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Tom Cato Amundsen
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
from __future__ import with_statement
import codecs
import glob
import logging
import os
import pprint
import sys
from solfege import filesystem
from solfege import lessonfile
from solfege.gpath import Path
import solfege
def mk_rel(filename, save_location):
assert os.path.isabs(filename) or filename.startswith(lessonfile.solfege_uri), filename
if filename.startswith(save_location):
return filename[len(save_location):]
return filename
def mk_abs(filename, save_location):
if not lessonfile.is_uri(filename) and not os.path.isabs(filename):
return os.path.join(save_location, filename)
return filename
def get_front_pages_list(debug):
files = []
def is_frontpage(fn):
# no subdirs
if not os.path.isfile(fn):
return False
# filter out bzr revert backups
if fn.endswith("~"):
return False
if os.path.split(fn)[1] == u"Makefile":
return False
return True
def add_subdir(subdir):
if os.path.isdir(subdir):
v = [os.path.join(subdir, fn) for fn in os.listdir(subdir)]
return [x for x in v if is_frontpage(x)]
return []
files = [fn for fn in glob.glob(os.path.join(u"exercises", "*", "*"))
if is_frontpage(fn)]
# The next line is for pre 3.15 compat. We used to place learning
# trees there.
files.extend(add_subdir(os.path.join(filesystem.app_data(), u"learningtrees")))
files.extend([
fn for fn in glob.glob(os.path.join(filesystem.app_data(), u"exercises", "*", "*"))
if is_frontpage(fn)])
if not debug:
try:
files.remove(os.path.join(lessonfile.exercises_dir, 'debugtree.txt'))
except ValueError:
# The debugtree.txtfile is for some reason missing
pass
return files
class _TreeCommon(list):
def __init__(self, items=[]):
if isinstance(items, _TreeCommon):
list.__init__(self, [items])
else:
list.__init__(self, items)
def dump(self, stream, level=0):
print >> stream, "%s%s([" % (" " * level, self.__class__.__name__)
self.dump_children(stream, level)
print >> stream, "%s ])," % (" " * level)
def dump_children(self, stream, level):
for child in self:
if isinstance(child, _TreeCommon):
child.dump(stream, level + 1)
else:
print >> stream, "%su'%s'," % (" " * (level + 1), child)
def iterate_filenames(self):
"""
Yield the filenames of each lesson that has been added to this
object or its children.
"""
for child in self:
if isinstance(child, _TreeCommon):
for c in child.iterate_filenames():
yield c
if isinstance(child, (str, unicode)):
yield child
def get_use_dict(self):
"""
Return a dict where the keys are the filename of lessons used
in this objects and its children. The values are an integer
value telling how many times it have been used.
"""
retval = {}
for filename in self.iterate_filenames():
retval[filename] = retval.get(filename, 0) + 1
return retval
def iterate_topics_for_file(self, filename):
for child in self:
if isinstance(child, (Page, Column, LinkList)):
for c in child.iterate_topics_for_file(filename):
yield c
elif child == filename:
yield self.m_name
def iterate_flattened(self):
for child in self:
yield child
if not isinstance(child, unicode):
for subchild in child.iterate_flattened():
yield subchild
@staticmethod
def tests_in_sub(sub):
if isinstance(sub, unicode):
try:
return bool(solfege.lessonfile.infocache.get(sub, 'test'))
except solfege.lessonfile.infocache.FileNotFound:
return False
for child in sub:
if _TreeCommon.tests_in_sub(child):
return True
return False
def foreach_file(self, callback, data=None):
try:
for idx, child in enumerate(self):
if getattr(self[idx], 'foreach_file', None):
self[idx].foreach_file(callback, data)
else:
if data:
self[idx] = callback(self[idx], data)
else:
self[idx] = callback(self[idx])
except TypeError:
pass
def get_topic_of_lesson_file(self, filename):
"""
Return the title of the first topic where filename is linked.
Return None if not found.
"""
for x in self.iterate_topics_for_file(filename):
return x
class _NamedTreeCommon(_TreeCommon):
def __init__(self, name=u'', listitems=[]):
assert isinstance(name, unicode)
_TreeCommon.__init__(self, listitems)
self.m_name = name
def dump(self, stream, level=0):
print >> stream, "%s%s(_(u'%s'), [" % (" " * level, self.__class__.__name__, self.m_name)
self.dump_children(stream, level)
print >> stream, "%s ])," % (" " * level)
class LinkList(_NamedTreeCommon):
"""
A list of links leading to exercises or new pages.
"""
def append(self, item):
assert isinstance(item, (str, unicode, Page))
super(LinkList, self).append(item)
def __str__(self):
return "LinkList(_('%s')# len: %i)" % (self.m_name, len(self))
class Column(_TreeCommon):
def add_linklist(self, heading):
self.append(LinkList(heading))
return self[-1]
def __str__(self):
return "Column(#len: %i)" % len(self)
class Page(_NamedTreeCommon):
def __init__(self, name=u'', listitems=[]):
assert isinstance(name, unicode)
_NamedTreeCommon.__init__(self, name, listitems)
self.m_modified = False
def __repr__(self):
return "<Page name=%s>" % self.m_name
def get(self, path):
"""
Return the element pointed to by path.
"""
elem = self
for idx in path[1:]:
elem = elem[idx]
return elem
def is_empty(self):
"""
Return True if all columns are empty or only contain empty sections.
"""
for col in self:
for sect in col:
if isinstance(sect, LinkList) and len(sect) != 0:
return False
return True
class Paragraph(object):
def __init__(self, text):
self.m_text = text
class FileHeader(_TreeCommon):
def __init__(self, version, page):
_TreeCommon.__init__(self, [page])
self.m_version = version
def dump(self, stream, level=0):
print >> stream, "%s%s(%s, " % (" " * level, self.__class__.__name__, self.m_version)
self.dump_children(stream, level)
print >> stream, "%s )" % (" " * level)
def save_file(self, filename):
"""
Rules:
* file is solfege: URI: do nothing
if old_location != save_location:
if old_location:
make filename absolute
make relto new location if possible
"""
save_location = os.path.split(filename)[0] + os.sep
self.foreach_file(mk_rel, save_location)
assert filename
f = codecs.open(filename, 'w', 'utf-8')
self.dump(f)
f.close()
self.foreach_file(mk_abs, save_location)
def convert_old_learningtree_data(data):
"""
fn is the filename of a learningtree file. It reads the file as
the default format was for solfege 3.14.
Returns the Page objects
"""
p = Page(_('Untitled%s' % ''), [Column()])
col = p[-1]
if isinstance(data, list):
if data[0] == 2: # Version 2
menudata = data[1]
elif (len(data) == 3 and isinstance(data[0], list)
and isinstance(data[1], dict)): # solfege 3.2.2
for section in data[0]:
linklist = col.add_linklist(unicode(section['name']))
for child in section['lessons']:
linklist.append(child)
return p
else:
menudata = data['menu']
for d in menudata:
linklist = col.add_linklist(unicode(d['name']))
for section in d['children']:
assert isinstance(section, dict)
# We create a new page with 1 col for each set of links
subpage = Page(unicode(section['name']))
subpage.append(Column())
subcol = subpage[-1]
sublinklist = subcol.add_linklist(unicode(section['name']))
for child in section['children']:
sublinklist.append(unicode(child))
linklist.append(subpage)
return p
def may_be_frontpage(filename):
"""
Return True if we think this is a front page file.
This is not a 100% test, but it should be safe enough to avoid
locking the program at startup if someone places a very large file
in the directory where front page files is supposed to be.
"""
with codecs.open(filename, "rU", 'utf-8', 'replace') as f:
s = f.readline().strip()
while s:
if s.startswith("#"):
s = f.readline().strip()
continue
# '{' is 3.14
# 'Page' is bzr branch 3.15.x
# 'FileHeader is 3.15.3 (or was it .2?)
if s.startswith(('{', 'Page', 'FileHeader')):
return True
logging.debug("my_be_frontpage(%s) => False" % filename)
return False
return False
def parse_tree(s, C_locale=False):
"""
This function can load both new (3.15.2) and older learning trees.
This function will convert old format to new format, and always return
new format files.
"""
if C_locale:
def ifu(s):
# We do the isinstance check because this way we can handle
# people manually editing the data file and adding strings that
# are plain ascii, but not marked as unicode ( u'' )
return s if isinstance(s, unicode) else unicode(s)
ifunc = ifu
else:
ifunc = _
namespace = {
'FileHeader': FileHeader,
'Page': Page,
'Column': Column,
'LinkList': LinkList,
'Paragraph': Paragraph,
'_': ifunc,
'__builtins__': {},
}
ret = eval(s, namespace, namespace)
if isinstance(ret, dict):
name = ret['title']
ret = convert_old_learningtree_data(ret)
ret.m_name = name
if isinstance(ret, FileHeader):
return ret[0]
else:
if not isinstance(ret, Page):
ret = convert_old_learningtree_data(ret)
# Convert from solfege 3.15.2 to 3.15.3 file format
# New front page files should store the filename, not the uuid
lessonfile.require_mgr()
def cnv_2_3(lesson_id):
try:
return lessonfile.mk_uri(lessonfile.mgr.get(lesson_id, 'filename'))
except lessonfile.mgr.UnknownLessonID, e:
return unicode(e)
ret.foreach_file(cnv_2_3)
return ret
def load_tree(fn, C_locale=False):
ret = parse_tree(codecs.open(fn, "rU", 'utf-8', 'replace').read(), C_locale)
# We store all files by absolute filename or solfege: uri internally
ret.foreach_file(mk_abs, os.path.split(fn)[0] + os.sep)
return ret
|