diff --git a/.gitignore b/.gitignore index f34fb22..fbb01e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,63 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj *.pyc + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Autosaved files +*~ + +# IDEs +CMakeLists.txt.user +.project +.cproject +.settings +.pydevproject + +# ROS +bin +build +lib +msg_gen +srv_gen +Makefile +CMakeCache.txt +cmake +cmake_install.cmake +gtest +catkin_generated +devel +test_results +CTestTestfile.cmake +CMakeFiles +catkin + +# Others *.egg-info/ -build/ dist/ doc/_* tmp/ diff --git a/kurt/__init__.py b/kurt/__init__.py index 95f7e42..2b05367 100644 --- a/kurt/__init__.py +++ b/kurt/__init__.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2012 Tim Radvan # # This file is part of Kurt. @@ -79,9 +82,11 @@ __version__ = '2.0.7' from collections import OrderedDict + import re import os import random + try: from cStringIO import StringIO except ImportError: @@ -91,14 +96,13 @@ import wave - #-- Utils --# def _clean_filename(name): """Strip non-alphanumeric characters to makes name safe to be used as filename.""" - return re.sub("[^\w .]", "", name) + return re.sub("[^\w .]", "", name) #-- Project: main class --# @@ -204,7 +208,7 @@ def __init__(self): def __repr__(self): return "<%s.%s()>" % (self.__class__.__module__, - self.__class__.__name__) + self.__class__.__name__) def get_sprite(self, name): """Get a sprite from :attr:`sprites` by name. @@ -212,6 +216,7 @@ def get_sprite(self, name): Returns None if the sprite isn't found. """ + for sprite in self.sprites: if sprite.name == name: return sprite @@ -225,6 +230,7 @@ def format(self): To convert to a different format, use :attr:`save()`. """ + if self._plugin: return self._plugin.name @@ -248,14 +254,18 @@ def load(cls, path, format=None): :raises: :py:class:`ValueError` if the format doesn't exist. """ + path_was_string = isinstance(path, basestring) if path_was_string: (folder, filename) = os.path.split(path) (name, extension) = os.path.splitext(filename) + if format is None: plugin = kurt.plugin.Kurt.get_plugin(extension=extension) + if not plugin: raise UnknownFormat(extension) + fp = open(path, "rb") else: fp = path @@ -268,15 +278,20 @@ def load(cls, path, format=None): project = plugin.load(fp) if path_was_string: fp.close() + project.convert(plugin) + if isinstance(path, basestring): project.path = path + if not project.name: project.name = name + return project def copy(self): """Return a new Project instance, deep-copying all the attributes.""" + p = Project() p.name = self.name p.path = self.path @@ -309,6 +324,7 @@ def copy(self): p.tempo = self.tempo p.notes = self.notes p.author = self.author + return p def convert(self, format): @@ -322,7 +338,9 @@ def convert(self, format): :raises: :class:`ValueError` if the format doesn't exist. """ + self._plugin = kurt.plugin.Kurt.get_plugin(format) + return list(self._normalize()) def save(self, path=None, debug=False): @@ -371,7 +389,7 @@ def save(self, path=None, debug=False): (name, extension) = os.path.splitext(filename) # get plugin from extension - if path: # only if not using self.path + if path: # only if not using self.path try: plugin = kurt.plugin.Kurt.get_plugin(extension=extension) except ValueError: @@ -396,9 +414,12 @@ def save(self, path=None, debug=False): for m in p.convert(plugin): print m + result = p._save(fp) + if path: fp.close() + return result if debug else p.path def _save(self, fp): @@ -443,12 +464,12 @@ def _normalize(self): for (name, var) in thing.variables.items(): if not var.watcher: var.watcher = kurt.Watcher(thing, - kurt.Block("var", name), is_visible=False) + kurt.Block("var", name), is_visible=False) self.actors.append(var.watcher) for (name, list_) in thing.lists.items(): if not list_.watcher: list_.watcher = kurt.Watcher(thing, - kurt.Block("list", name), is_visible=False) + kurt.Block("list", name), is_visible=False) self.actors.append(list_.watcher) # notes - line endings @@ -461,10 +482,10 @@ def convert_block(block): if isinstance(block.type, CustomBlockType): if "Custom Blocks" not in self._plugin.features: raise BlockNotSupported( - "%s doesn't support custom blocks" - % self._plugin.display_name) + "%s doesn't support custom blocks" + % self._plugin.display_name) - else: # BlockType + else: # BlockType pbt = block.type.convert(self._plugin) except BlockNotSupported, err: err.message += ". Caused by: %r" % block @@ -531,13 +552,14 @@ class UnsupportedFeature(object): Output once by Project.convert for each occurence of the feature. """ + def __init__(self, feature, obj): self.feature = kurt.plugin.Feature.get(feature) self.obj = obj def __repr__(self): return "<%s.%s(%s)>" % (self.__class__.__module__, - self.__class__.__name__, unicode(self)) + self.__class__.__name__, unicode(self)) def __str__(self): return "UnsupportedFeature: %s" % unicode(self) @@ -546,7 +568,6 @@ def __unicode__(self): return u"%r: %r" % (self.feature.name, self.obj) - #-- Errors --# class UnknownFormat(Exception): @@ -556,6 +577,7 @@ class UnknownFormat(Exception): file extension. """ + pass @@ -574,6 +596,7 @@ class BlockNotSupported(Exception): :class:`PluginBlockType` for the given plugin. """ + pass @@ -584,8 +607,8 @@ class VectorImageError(Exception): give a warning instead when the Project is converted. """ - pass + pass #-- Actors & Scriptables --# @@ -674,7 +697,10 @@ def _normalize(self): def copy(self, o=None): """Return a new instance, deep-copying all the attributes.""" - if o is None: o = self.__class__(self.project) + + if o is None: + o = self.__class__(self.project) + o.scripts = [s.copy() for s in self.scripts] o.variables = dict((n, v.copy()) for (n, v) in self.variables.items()) o.lists = dict((n, l.copy()) for (n, l) in self.lists.items()) @@ -682,6 +708,7 @@ def copy(self, o=None): o.sounds = [s.copy() for s in self.sounds] o.costume_index = self.costume_index o.volume = self.volume + return o @property @@ -691,6 +718,7 @@ def costume_index(self): None if no costume is selected. """ + if self.costume: return self.costumes.index(self.costume) @@ -708,6 +736,7 @@ def parse(self, text): reference. """ + self.scripts.append(kurt.text.parse(text, self)) @@ -741,6 +770,7 @@ def __init__(self, project): @property def backgrounds(self): """Alias for :attr:`costumes`.""" + return self.costumes @backgrounds.setter @@ -749,11 +779,12 @@ def backgrounds(self, value): def __repr__(self): return "<%s.%s()>" % (self.__class__.__module__, - self.__class__.__name__) + self.__class__.__name__) def _normalize(self): if not self.costume and not self.costumes: self.costume = Costume("blank", Image.new(self.SIZE, self.COLOR)) + Scriptable._normalize(self) @@ -824,6 +855,7 @@ def _normalize(self): def copy(self): """Return a new instance, deep-copying all the attributes.""" + o = self.__class__(self.project, self.name) Scriptable.copy(self, o) o.position = tuple(self.position) @@ -832,11 +864,12 @@ def copy(self): o.size = self.size o.is_draggable = self.is_draggable o.is_visible = self.is_visible + return o def __repr__(self): return "<%s.%s(%r)>" % (self.__class__.__module__, - self.__class__.__name__, self.name) + self.__class__.__name__, self.name) class Watcher(Actor): @@ -848,7 +881,7 @@ class Watcher(Actor): """ def __init__(self, target, block, style="normal", is_visible=True, - pos=None): + pos=None): Actor.__init__(self) assert target is not None @@ -912,18 +945,21 @@ def __init__(self, target, block, style="normal", is_visible=True, def _normalize(self): assert self.style in ("normal", "large", "slider") + if self.value: self.value.watcher = self def copy(self): """Return a new instance with the same attributes.""" + o = self.__class__(self.target, - self.block.copy(), - self.style, - self.is_visible, - self.pos) + self.block.copy(), + self.style, + self.is_visible, + self.pos) o.slider_min = self.slider_min o.slider_max = self.slider_max + return o @property @@ -935,6 +971,7 @@ def kind(self): ``block`` watchers watch the value of a reporter block. """ + if self.block.type.has_command('readVariable'): return 'variable' elif self.block.type.has_command('contentsOfList:'): @@ -949,6 +986,7 @@ def value(self): Returns ``None`` if it's a block watcher. """ + if self.kind == 'variable': return self.target.variables[self.block.args[0]] elif self.kind == 'list': @@ -956,7 +994,7 @@ def value(self): def __repr__(self): r = "%s.%s(%r, %r" % (self.__class__.__module__, - self.__class__.__name__, self.target, self.block) + self.__class__.__name__, self.target, self.block) if self.style != "normal": r += ", style=%r" % self.style if not self.is_visible: @@ -964,8 +1002,8 @@ def __repr__(self): if self.pos: r += ", pos=%s" % repr(self.pos) r += ")" - return r + return r #-- Variables --# @@ -1001,14 +1039,16 @@ def __init__(self, value=0, is_cloud=False): def copy(self): """Return a new instance with the same attributes.""" + return self.__class__(self.value, self.is_cloud) def __repr__(self): r = "%s.%s(%r" % (self.__class__.__module__, self.__class__.__name__, - self.value) + self.value) if self.is_cloud: r += ", is_cloud=%r" % self.is_cloud r += ")" + return r @@ -1021,6 +1061,7 @@ class List(object): list values, and this class is not used. """ + def __init__(self, items=None, is_cloud=False): self.items = list(items) if items else [] """The items contained in the list. A Python list of unicode @@ -1049,12 +1090,12 @@ def copy(self): def __repr__(self): r = "<%s.%s(%i items)>" % (self.__class__.__module__, - self.__class__.__name__, len(self.items)) + self.__class__.__name__, len(self.items)) if self.is_cloud: r += ", is_cloud=%r" % self.is_cloud r += ")" - return r + return r #-- Color --# @@ -1101,6 +1142,7 @@ def __init__(self, r, g=None, b=None): @property def value(self): """Return ``(r, g, b)`` tuple.""" + return (self.r, self.g, self.b) @value.setter @@ -1118,7 +1160,7 @@ def __iter__(self): def __repr__(self): return "%s.%s(%s)" % (self.__class__.__module__, - self.__class__.__name__, repr(self.value).strip("()")) + self.__class__.__name__, repr(self.value).strip("()")) def stringify(self): """Returns the color value in hexcode format. @@ -1126,18 +1168,21 @@ def stringify(self): eg. ``'#ff1056'`` """ + hexcode = "#" for x in self.value: part = hex(x)[2:] - if len(part) < 2: part = "0" + part + if len(part) < 2: + part = "0" + part hexcode += part + return hexcode @classmethod def random(cls): f = lambda: random.randint(0, 255) - return cls(f(), f(), f()) + return cls(f(), f(), f()) #-- BlockTypes --# @@ -1150,7 +1195,7 @@ class Insert(object): 'number-menu': 0, 'stack': [], 'color': Color('#f00'), - 'inline': 'nil', # Can't be empty + 'inline': 'nil', # Can't be empty } SHAPE_FMTS = { @@ -1167,39 +1212,39 @@ class Insert(object): KIND_OPTIONS = { 'attribute': ['x position', 'y position', 'direction', 'costume #', - 'size', 'volume'], + 'size', 'volume'], 'backdrop': [], 'booleanSensor': ['button pressed', 'A connected', 'B connected', - 'C connected', 'D connected'], + 'C connected', 'D connected'], 'broadcast': [], 'costume': [], 'direction': [], 'drum': range(1, 18), 'effect': ['color', 'fisheye', 'whirl', 'pixelate', 'mosaic', - 'brightness', 'ghost'], + 'brightness', 'ghost'], 'instrument': range(1, 21), 'key': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', - 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'space', - 'left arrow', 'right arrow', 'up arrow', 'down arrow'], + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'space', + 'left arrow', 'right arrow', 'up arrow', 'down arrow'], 'list': [], 'listDeleteItem': ['last', 'all'], 'listItem': ['last', 'random'], 'mathOp': ['abs', 'floor', 'ceiling', 'sqrt', 'sin', 'cos', 'tan', - 'asin', 'acos', 'atan', 'ln', 'log', 'e ^', '10 ^'], + 'asin', 'acos', 'atan', 'ln', 'log', 'e ^', '10 ^'], 'motorDirection': ['this way', 'that way', 'reverse'], 'note': [], 'rotationStyle': ['left-right', "don't rotate", 'all around'], 'sensor': ['slider', 'light', 'sound', 'resistance-A', 'resistance-B', - 'resistance-C', 'resistance-D'], + 'resistance-C', 'resistance-D'], 'sound': [], 'spriteOnly': ['myself'], 'spriteOrMouse': ['mouse-pointer'], 'spriteOrStage': ['Stage'], - 'stageOrThis': ['Stage'], # ? TODO + 'stageOrThis': ['Stage'], # ? TODO 'stop': ['all', 'this script', 'other scripts in sprite'], 'timeAndDate': ['year', 'month', 'date', 'day of week', 'hour', - 'minute', 'second'], + 'minute', 'second'], 'touching': ['mouse-pointer', 'edge'], 'triggerSensor': ['loudness', 'timer', 'video motion'], 'var': [], @@ -1208,7 +1253,7 @@ class Insert(object): } def __init__(self, shape, kind=None, default=None, name=None, - unevaluated=None): + unevaluated=None): self.shape = shape """What kind of values this argument accepts. @@ -1316,16 +1361,21 @@ def __init__(self, shape, kind=None, default=None, name=None, def __repr__(self): r = "%s.%s(%r" % (self.__class__.__module__, - self.__class__.__name__, self.shape) + self.__class__.__name__, self.shape) + if self.kind != None: r += ", %r" % self.kind + if self.default != Insert.SHAPE_DEFAULTS.get(self.shape, None): r += ", default=%r" % self.default + if self.unevaluated: r += ", unevaluated=%r" % self.unevaluated + if self.name: r += ", name=%r" % self.name r += ")" + return r def __eq__(self, other): @@ -1346,15 +1396,18 @@ def copy(self): def stringify(self, value=None, block_plugin=False): if value is None or (value is False and self.shape == "boolean"): value = self.default + if value is None: value = "" - if isinstance(value, Block): # use block's shape + + if isinstance(value, Block): # use block's shape return value.stringify(block_plugin, in_insert=True) else: if hasattr(value, "stringify"): value = value.stringify() elif isinstance(value, list): - value = "\n".join(block.stringify(block_plugin) for block in value) + value = "\n".join(block.stringify(block_plugin) + for block in value) if self.shape == 'stack': value = value.replace("\n", "\n ") @@ -1363,10 +1416,12 @@ def stringify(self, value=None, block_plugin=False): value = Insert.SHAPE_FMTS.get(self.shape, '%s') % (value,) elif self.shape == 'string' or self.kind == 'broadcast': value = unicode(value) + if "'" in value: value = '"%s"' % value.replace('"', '\\"') else: value = "'%s'" % value.replace("'", "\\'") + return value def options(self, scriptable=None): @@ -1376,6 +1431,7 @@ def options(self, scriptable=None): Mostly complete, excepting 'attribute'. """ + options = list(Insert.KIND_OPTIONS.get(self.kind, [])) if scriptable: if self.kind == 'var': @@ -1392,12 +1448,13 @@ def options(self, scriptable=None): options += [c.name for c in scriptable.sounds] options += [c.name for c in scriptable.project.stage.sounds] elif self.kind in ('spriteOnly', 'spriteOrMouse', 'spriteOrStage', - 'touching'): + 'touching'): options += [s.name for s in scriptable.project.sprites] elif self.kind == 'attribute': - pass # TODO + pass # TODO elif self.kind == 'broadcast': options += list(set(scriptable.project.get_broadcasts())) + return options @@ -1460,8 +1517,10 @@ def text(self): eg. ``'say %s for %s secs'`` """ + parts = [("%s" if isinstance(p, Insert) else p) for p in self.parts] - parts = [("%%" if p == "%" else p) for p in parts] # escape percent + parts = [("%%" if p == "%" else p) for p in parts] # escape percent + return "".join(parts) @property @@ -1471,11 +1530,13 @@ def inserts(self): List of :class:`Insert` instances. """ + return [p for p in self.parts if isinstance(p, Insert)] @property def defaults(self): """Default values for block inserts. (See :attr:`Block.args`.)""" + return [i.default for i in self.inserts] @property @@ -1485,28 +1546,34 @@ def stripped_text(self): Used by :class:`BlockType.get` to look up blocks. """ + return BaseBlockType._strip_text( - self.text % tuple((i.default if i.shape == 'inline' else '%s') - for i in self.inserts)) + self.text % tuple((i.default if i.shape == 'inline' else '%s') + for i in self.inserts)) @staticmethod def _strip_text(text): """Returns text with spaces and inserts removed.""" + text = re.sub(r'[ ,?:]|%s', "", text.lower()) + for chr in "-%": new_text = text.replace(chr, "") if new_text: text = new_text + return text.lower() def __repr__(self): return "<%s.%s(%r shape=%r)>" % (self.__class__.__module__, - self.__class__.__name__, - self.text % tuple(i.stringify(None) for i in self.inserts), - self.shape) + self.__class__.__name__, + self.text % tuple( + i.stringify(None) for i in self.inserts), + self.shape) def stringify(self, args=None, block_plugin=False, in_insert=False): - if args is None: args = self.defaults + if args is None: + args = self.defaults args = list(args) r = self.text % tuple(i.stringify(args.pop(0), block_plugin) @@ -1516,8 +1583,10 @@ def stringify(self, args=None, block_plugin=False, in_insert=False): return r + "end" fmt = BaseBlockType.SHAPE_FMTS.get(self.shape, "%s") + if not block_plugin: fmt = "%s" if fmt == "%s" else "(%s)" + if in_insert and fmt == "%s": fmt = "{%s}" @@ -1525,9 +1594,11 @@ def stringify(self, args=None, block_plugin=False, in_insert=False): def has_insert(self, shape): """Returns True if any of the inserts have the given shape.""" + for insert in self.inserts: if insert.shape == shape: return True + return False @@ -1542,8 +1613,10 @@ class BlockType(BaseBlockType): def __getstate__(self): """lambda functions are not pickleable so drop them.""" + copy = self.__dict__.copy() copy['_workaround'] = None + return copy def __init__(self, pbt): @@ -1563,10 +1636,12 @@ def _add_conversion(self, plugin, pbt): """ assert self.shape == pbt.shape assert len(self.inserts) == len(pbt.inserts) + for (i, o) in zip(self.inserts, pbt.inserts): assert i.shape == o.shape assert i.kind == o.kind assert i.unevaluated == o.unevaluated + if plugin not in self._plugins: self._plugins[plugin] = pbt @@ -1576,14 +1651,17 @@ def convert(self, plugin=None): If plugin is ``None``, return the first registered plugin. """ + if plugin: plugin = kurt.plugin.Kurt.get_plugin(plugin) + if plugin.name in self._plugins: return self._plugins[plugin.name] else: err = BlockNotSupported("%s doesn't have %r" % - (plugin.display_name, self)) + (plugin.display_name, self)) err.block_type = self + raise err else: return self.conversions[0] @@ -1591,18 +1669,23 @@ def convert(self, plugin=None): @property def conversions(self): """Return the list of :class:`PluginBlockType` instances.""" + return self._plugins.values() def has_conversion(self, plugin): """Return True if the plugin supports this block.""" + plugin = kurt.plugin.Kurt.get_plugin(plugin) + return plugin.name in self._plugins def has_command(self, command): """Returns True if any of the plugins have the given command.""" + for pbt in self._plugins.values(): if pbt.command == command: return True + return False @property @@ -1629,23 +1712,28 @@ def get(cls, block_type): corresponding BlockType. """ + if isinstance(block_type, (BlockType, CustomBlockType)): return block_type if isinstance(block_type, PluginBlockType): block_type = block_type.command + # remove odd characters + block_type = block_type.replace("\x1f", "/") + block = kurt.plugin.Kurt.block_by_command(block_type) + if block: return block blocks = kurt.plugin.Kurt.blocks_by_text(block_type) - for block in blocks: # check the blocks' commands map to unique blocks + for block in blocks: # check the blocks' commands map to unique blocks if kurt.plugin.Kurt.block_by_command( block.convert().command) != blocks[0]: raise ValueError( - "ambigious block text %r, use one of %r instead" % - (block_type, [b.convert().command for b in blocks])) + "ambigious block text %r, use one of %r instead" % + (block_type, [b.convert().command for b in blocks])) if blocks: return blocks[0] @@ -1658,6 +1746,7 @@ def __eq__(self, other): for plugin in self._plugins: if plugin in other._plugins: return self._plugins[plugin] == other._plugins[plugin] + return False def __ne__(self, other): @@ -1724,10 +1813,11 @@ def __eq__(self, other): return True elif isinstance(other, PluginBlockType): for name in ("shape", "inserts", "command", "format", "category"): - if getattr(self, name) != getattr(other, name): + if getattr(self, name) != getattr(other, name): return False else: return True + return False @@ -1754,7 +1844,6 @@ def __init__(self, shape, parts): """True if the block should run without screen refresh.""" - #-- Scripts --# class Block(object): @@ -1843,6 +1932,7 @@ def _normalize(self): def copy(self): """Return a new Block instance with the same attributes.""" + args = [] for arg in self.args: if isinstance(arg, Block): @@ -1850,23 +1940,22 @@ def copy(self): elif isinstance(arg, list): arg = [b.copy() for b in arg] args.append(arg) + return Block(self.type, *args) def __eq__(self, other): - return ( - isinstance(other, Block) and - self.type == other.type and - self.args == other.args - ) + return (isinstance(other, Block) and + self.type == other.type and + self.args == other.args) def __ne__(self, other): return not self == other def __repr__(self): string = "%s.%s(%s, " % (self.__class__.__module__, - self.__class__.__name__, - repr(self.type.convert().command if isinstance(self.type, - BlockType) else self.type)) + self.__class__.__name__, + repr(self.type.convert().command if isinstance(self.type, + BlockType) else self.type)) for arg in self.args: if isinstance(arg, Block): string = string.rstrip("\n") @@ -1876,24 +1965,31 @@ def __repr__(self): string += " " else: string += " " + string += "[\n" + for block in arg: string += " " string += repr(block).replace("\n", "\n ") string += ",\n" + string += " ], " else: string += repr(arg) + ", " + string = string.rstrip(" ").rstrip(",") + return string + ")" def stringify(self, block_plugin=False, in_insert=False): s = self.type.stringify(self.args, block_plugin, in_insert) + if self.comment: i = s.index("\n") if "\n" in s else len(s) - indent = "\n" + " " * i + " // " + indent = "\n" + " " * i + " // " comment = " // " + self.comment.replace("\n", indent) s = s[:i] + comment + s[i:] + return s @@ -1923,31 +2019,35 @@ def __init__(self, blocks=None, pos=None): def _normalize(self): self.pos = self.pos self.blocks = list(self.blocks) + for block in self.blocks: block._normalize() def copy(self): """Return a new instance with the same attributes.""" + return self.__class__([b.copy() for b in self.blocks], - tuple(self.pos) if self.pos else None) + tuple(self.pos) if self.pos else None) def __eq__(self, other): - return ( - isinstance(other, Script) and - self.blocks == other.blocks - ) + return (isinstance(other, Script) and + self.blocks == other.blocks) def __ne__(self, other): return not self == other def __repr__(self): r = "%s.%s([\n" % (self.__class__.__module__, - self.__class__.__name__) + self.__class__.__name__) + for block in self.blocks: r += " " + repr(block).replace("\n", "\n ") + ",\n" + r = r.rstrip().rstrip(",") + "]" + if self.pos: r += ", pos=%r" % (self.pos,) + return r + ")" def stringify(self, block_plugin=False): @@ -1959,6 +2059,7 @@ def stringify(self, block_plugin=False): def __getattr__(self, name): if name.startswith('__') and name.endswith('__'): return super(Script, self).__getattr__(name) + return getattr(self.blocks, name) def __iter__(self): @@ -1995,9 +2096,11 @@ def copy(self): def __repr__(self): r = "%s.%s(%r" % (self.__class__.__module__, - self.__class__.__name__, self.text) + self.__class__.__name__, self.text) + if self.pos: r += ", pos=%r" % (self.pos,) + return r + ")" def stringify(self): @@ -2008,7 +2111,6 @@ def _normalize(self): self.text = unicode(self.text) - #-- Costumes --# class Costume(object): @@ -2024,6 +2126,7 @@ def __init__(self, name, image, rotation_center=None): if not rotation_center: rotation_center = (int(image.width / 2), int(image.height / 2)) + self.rotation_center = tuple(rotation_center) """``(x, y)`` position from the top-left corner of the point about which the image rotates. @@ -2037,6 +2140,7 @@ def __init__(self, name, image, rotation_center=None): def copy(self): """Return a new instance with the same attributes.""" + return Costume(self.name, self.image, self.rotation_center) @classmethod @@ -2047,8 +2151,10 @@ def load(self, path): image filename. """ + (folder, filename) = os.path.split(path) (name, extension) = os.path.splitext(filename) + return Costume(name, Image.load(path)) def save(self, path): @@ -2064,24 +2170,31 @@ def save(self, path): """ (folder, filename) = os.path.split(path) + if not filename: filename = _clean_filename(self.name) path = os.path.join(folder, filename) + return self.image.save(path) def resize(self, size): """Resize :attr:`image` in-place.""" + self.image = self.image.resize(size) def __repr__(self): return "<%s.%s name=%r rotation_center=%d,%d at 0x%X>" % ( - self.__class__.__module__, self.__class__.__name__, self.name, - self.rotation_center[0], self.rotation_center[1], id(self) - ) + self.__class__.__module__, + self.__class__.__name__, + self.name, + self.rotation_center[0], + self.rotation_center[1], + id(self)) def __getattr__(self, name): if name in ('width', 'height', 'size'): return getattr(self.image, name) + return super(Costume, self).__getattr__(name) @@ -2118,6 +2231,7 @@ def __init__(self, contents, format=None): self._contents = None self._format = None self._size = None + if isinstance(contents, PIL.Image.Image): self._pil_image = contents else: @@ -2132,10 +2246,12 @@ def __getstate__(self): 'size': self._pil_image.size, 'mode': self._pil_image.mode} return copy + return self.__dict__ def __setstate__(self, data): self.__dict__.update(data) + if self._pil_image: self._pil_image = PIL.Image.frombytes(**self._pil_image) @@ -2144,15 +2260,18 @@ def __setstate__(self, data): @property def pil_image(self): """A :class:`PIL.Image.Image` instance containing the image data.""" + if not self._pil_image: if self._format == "SVG": raise VectorImageError("can't rasterise vector images") self._pil_image = PIL.Image.open(StringIO(self.contents)) + return self._pil_image @property def contents(self): """The raw file contents as a string.""" + if not self._contents: if self._path: # Read file into memory so we don't run out of file descriptors @@ -2164,6 +2283,7 @@ def contents(self): f = StringIO() self._pil_image.save(f, self.format) self._contents = f.getvalue() + return self._contents @property @@ -2175,6 +2295,7 @@ def format(self): ``"JPEG"`` and ``"PNG"``. """ + if self._format: return self._format elif self.pil_image: @@ -2187,11 +2308,13 @@ def extension(self): eg ``".png"`` """ + return Image.image_extension(self.format) @property def size(self): """``(width, height)`` in pixels.""" + if self._size and not self._pil_image: return self._size else: @@ -2231,8 +2354,10 @@ def convert(self, *formats): last format. """ + for format in formats: format = Image.image_format(format) + if self.format == format: return self else: @@ -2244,11 +2369,13 @@ def _convert(self, format): Returns self if the format is already the same. """ + if self.format == format: return self else: image = Image(self.pil_image) image._format = format + return image def save(self, path): @@ -2260,6 +2387,7 @@ def save(self, path): :returns: Path to the saved file. """ + (folder, filename) = os.path.split(path) (name, extension) = os.path.splitext(filename) @@ -2286,10 +2414,12 @@ def save(self, path): @classmethod def new(self, size, fill): """Return a new Image instance filled with a color.""" + return Image(PIL.Image.new("RGB", size, fill)) def resize(self, size): """Return a new Image instance with the given size.""" + return Image(self.pil_image.resize(size, PIL.Image.ANTIALIAS)) def paste(self, other): @@ -2298,9 +2428,12 @@ def paste(self, other): This image will show through transparent areas of the given image. """ + r, g, b, alpha = other.pil_image.split() + pil_image = self.pil_image.copy() pil_image.paste(other.pil_image, mask=alpha) + return kurt.Image(pil_image) # Static methods @@ -2309,18 +2442,21 @@ def paste(self, other): def image_format(format_or_extension): if format_or_extension: format = format_or_extension.lstrip(".").upper() + if format == "JPG": format = "JPEG" + return format @staticmethod def image_extension(format_or_extension): if format_or_extension: extension = format_or_extension.lstrip(".").lower() + if extension == "jpeg": extension = "jpg" - return "." + extension + return "." + extension #-- Sounds --# @@ -2341,6 +2477,7 @@ def __init__(self, name, waveform): def copy(self): """Return a new instance with the same attributes.""" + return Sound(self.name, self.waveform) @classmethod @@ -2351,8 +2488,10 @@ def load(self, path): the sound filename. """ + (folder, filename) = os.path.split(path) (name, extension) = os.path.splitext(filename) + return Sound(name, Waveform.load(path)) def save(self, path): @@ -2364,15 +2503,18 @@ def save(self, path): :returns: Path to the saved file. """ + (folder, filename) = os.path.split(path) + if not filename: filename = _clean_filename(self.name) path = os.path.join(folder, filename) + return self.waveform.save(path) def __repr__(self): return "<%s.%s name=%r at 0x%X>" % (self.__class__.__module__, - self.__class__.__name__, self.name, id(self)) + self.__class__.__name__, self.name, id(self)) class Waveform(object): @@ -2410,11 +2552,13 @@ def contents(self): f = open(self._path, "rb") self._contents = f.read() f.close() + return self._contents @property def _wave(self): """Return a wave.Wave_read instance from the ``wave`` module.""" + try: return wave.open(StringIO(self.contents)) except wave.Error, err: @@ -2425,6 +2569,7 @@ def _wave(self): @property def rate(self): """The sampling rate of the sound.""" + if self._rate: return self._rate else: @@ -2433,17 +2578,18 @@ def rate(self): @property def sample_count(self): """The number of samples in the sound.""" + if self._sample_count: return self._sample_count else: return self._wave.getnframes() - # Methods @classmethod def load(cls, path): """Load Waveform from file.""" + assert os.path.exists(path), "No such file: %r" % path (folder, filename) = os.path.split(path) @@ -2451,6 +2597,7 @@ def load(cls, path): wave = Waveform(None) wave._path = path + return wave def save(self, path): @@ -2459,6 +2606,7 @@ def save(self, path): :returns: Path to the saved file. """ + (folder, filename) = os.path.split(path) (name, extension) = os.path.splitext(filename) @@ -2473,7 +2621,6 @@ def save(self, path): return path - #-- Import submodules --# import kurt.plugin diff --git a/kurt/plugin.py b/kurt/plugin.py index 34d769c..c091337 100644 --- a/kurt/plugin.py +++ b/kurt/plugin.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2012 Tim Radvan # # This file is part of Kurt. @@ -67,7 +70,6 @@ def save(self, fp, kurt_project): import kurt - class KurtPlugin(object): """Handles a specific file format. @@ -123,6 +125,7 @@ def load(self, fp): :returns: :class:`Project` """ + raise NotImplementedError def save(self, fp, project): @@ -132,6 +135,7 @@ def save(self, fp, project): :param project: a :class:`Project` """ + raise NotImplementedError @@ -157,6 +161,7 @@ def register(cls, plugin): * :attr:`Project.convert` is called with the format as a parameter """ + cls.plugins[plugin.name] = plugin # make features @@ -203,6 +208,7 @@ def get_plugin(cls, name=None, **kwargs): :returns: :class:`KurtPlugin` """ + if isinstance(name, KurtPlugin): return name @@ -229,6 +235,7 @@ def block_by_command(cls, command): Returns None if the block is not found. """ + for block in cls.blocks: if block.has_command(command): return block @@ -240,6 +247,7 @@ def blocks_by_text(cls, text): Capitalisation and spaces are ignored. """ + text = kurt.BlockType._strip_text(text) matches = [] for block in cls.blocks: @@ -250,11 +258,11 @@ def blocks_by_text(cls, text): return matches - #-- Features --# def empty_generator(): - if False: yield + if False: + yield class Feature(object): @@ -266,11 +274,13 @@ class Feature(object): def get(cls, name): if isinstance(name, Feature): return name + return cls.FEATURES[name] def __init__(self, name, description): self.name = name self.description = description + Feature.FEATURES[name] = self def __repr__(self): @@ -279,41 +289,53 @@ def __repr__(self): def __eq__(self, other): if isinstance(other, basestring): return self.name == other + return self is other def normalize(self, project): """Convert project to a plugin that SUPPORTS this feature.""" + return empty_generator() def workaround(self, project): """Convert project to a plugin that does NOT support this feature.""" + return empty_generator() def workaround(feature): feature = Feature.get(feature) + def _wrapper(f): assert callable(f) + feature.workaround = f + return _wrapper + def normalize(feature): feature = Feature.get(feature) + def _wrapper(f): assert callable(f) + feature.normalize = f - return _wrapper + return _wrapper Feature("Vector Images", """Allow vector format (SVG) image files for costumes.""") + @workaround("Vector Images") def _workaround_no_vector_images(project): """Replace vector images with fake ones.""" + RED = (255, 0, 0) PLACEHOLDER = kurt.Image.new((32, 32), RED) + for scriptable in [project.stage] + project.sprites: for costume in scriptable.costumes: if costume.image.format == "SVG": @@ -324,13 +346,17 @@ def _workaround_no_vector_images(project): """Can have stage-specific variables and lists, in addition to global variables and lists (which are stored on the :class:`Project`).""") + @workaround("Stage-specific Variables") def _workaround_no_stage_specific_variables(project): """Make Stage-specific variables global (move them to Project).""" + for (name, var) in project.stage.variables.items(): yield "variable %s" % name + for (name, _list) in project.stage.lists.items(): yield "list %s" % name + project.variables.update(project.stage.variables) project.lists.update(project.stage.lists) project.stage.variables = {} @@ -344,16 +370,19 @@ def _workaround_no_stage_specific_variables(project): """Variables can take list values. Nested lists are supported. `Scriptable.lists` and `Project.lists` are unused.""") + @workaround("First-class Lists") def _workaround_no_first_class_lists(project): """Replace list-containing variables with lists of the same name.""" - return empty_generator() # TODO + + return empty_generator() # TODO + @normalize("First-class Lists") def _normalize_first_class_lists(project): """Replace `Scriptable.lists` with variables containing lists.""" - return empty_generator() # TODO + return empty_generator() # TODO #-- Convert Blocks --# @@ -364,5 +393,6 @@ def block_workaround(bt, workaround): workaround = lambda block: w.copy() else: assert callable(workaround) + bt = kurt.BlockType.get(bt) bt._add_workaround(workaround) diff --git a/kurt/scratch14/__init__.py b/kurt/scratch14/__init__.py index 4c68490..98b95cb 100644 --- a/kurt/scratch14/__init__.py +++ b/kurt/scratch14/__init__.py @@ -38,7 +38,6 @@ # counterparts. Array and Dictionary are converted to list and dict. - #-- Hack Container repr to be pretty --# @recursion_lock("<...>") @@ -51,7 +50,6 @@ def container_repr(self): Container.__repr__ = container_repr - #-- Utils --# def get_blocks_by_id(this_block): @@ -70,16 +68,16 @@ def get_blocks_by_id(this_block): for b in get_blocks_by_id(block): yield b + def swap_byte_pairs(data): swapped_bytes = "" for i in range(0, len(data), 2): - a = data[i:i+1] - b = data[i+1:i+2] + a = data[i:i + 1] + b = data[i + 1:i + 2] swapped_bytes += b + a return swapped_bytes - #-- Main class --# # kurt_* -- objects from kurt 2.0 api @@ -112,11 +110,12 @@ def load(self, fp): thumbnail = self.info['thumbnail'] if thumbnail and isinstance(thumbnail, Form): thumbnail = self.UserObject('ImageMedia', - name = 'thumbnail', - form = thumbnail, - ) + name='thumbnail', + form=thumbnail, + ) thumbnail_costume = self.load_image(thumbnail) - if thumbnail_costume: self.project.thumbnail = thumbnail_costume.image + if thumbnail_costume: + self.project.thumbnail = thumbnail_costume.image # stage self.load_scriptable(self.project.stage, self.stage) @@ -140,7 +139,7 @@ def load(self, fp): # TODO: stacking order of actors. - self.project._original = (self.info, self.stage) # DEBUG + self.project._original = (self.info, self.stage) # DEBUG return self.project @@ -151,8 +150,8 @@ def save(self, fp, project): # project info thumbnail = self.save_image(kurt.Costume("thumbnail", ( - self.project.thumbnail or kurt.Image.new((160, 120), (1, 1, 1)) - ))).form + self.project.thumbnail or kurt.Image.new((160, 120), (1, 1, 1)) + ))).form self.info = { 'author': self.project.author, @@ -201,8 +200,8 @@ def save(self, fp, project): self.save_watcher(kurt_actor)) v14_project = Container( - info = encode_obj_table(self.info, self.plugin), - stage = encode_obj_table(self.stage, self.plugin), + info=encode_obj_table(self.info, self.plugin), + stage=encode_obj_table(self.stage, self.plugin), ) scratch_file.build_stream(v14_project, fp) @@ -243,9 +242,10 @@ def save_image(self, kurt_costume): if image.format == "JPEG": v14_image = self.UserObject("ImageMedia", - name = unicode(kurt_costume.name), - jpegBytes = ByteArray(kurt_costume.image.contents), - ) + name=unicode(kurt_costume.name), + jpegBytes=ByteArray( + kurt_costume.image.contents), + ) else: pil_image = kurt_costume.image.pil_image pil_image = pil_image.convert("RGBA") @@ -253,9 +253,10 @@ def save_image(self, kurt_costume): rgba_string = pil_image.tobytes() v14_image = self.UserObject("ImageMedia", - name = unicode(kurt_costume.name), - form = Form.from_string(width, height, rgba_string), - ) + name=unicode(kurt_costume.name), + form=Form.from_string( + width, height, rgba_string), + ) v14_image.size = kurt_costume.image.size v14_image.rotationCenter = Point(kurt_costume.rotation_center) @@ -267,7 +268,7 @@ def load_sound(self, v14_sound): f.setnframes(v14_sound.originalSound.samplesSize) f.setframerate(v14_sound.originalSound.originalSamplingRate) f.setnchannels(1) - f.setsampwidth(2) # bytes? + f.setsampwidth(2) # bytes? f.writeframes(swap_byte_pairs(v14_sound.originalSound.samples.value)) f.close() return kurt.Sound(v14_sound.name, kurt.Waveform(contents.getvalue())) @@ -376,10 +377,10 @@ def save_block(self, kurt_block): arg = Color.from_8bit(arg) elif insert: if insert.kind in ('mathOp', 'effect', 'key'): - arg = str(arg) # Won't accept unicode + arg = str(arg) # Won't accept unicode elif insert.kind in ('spriteOrMouse', 'spriteOrStage', - 'touching'): + 'touching'): if arg == 'mouse-pointer': arg = Symbol('mouse') elif arg == 'edge': @@ -427,7 +428,7 @@ def load_lists(self, v14_lists, kurt_target): kurt_target.lists[v14_list.name] = kurt_list kurt_watcher = kurt.Watcher(kurt_target, - kurt.Block("contentsOfList:", v14_list.name)) + kurt.Block("contentsOfList:", v14_list.name)) kurt_watcher.is_visible = bool(v14_list.owner) (x, y, w, h) = v14_list.bounds.value @@ -441,9 +442,10 @@ def save_lists(self, kurt_target, v14_morph): for (name, kurt_list) in kurt_target.lists.items(): name = unicode(name) v14_list = self.UserObject("ScratchListMorph", - name = name, - list_items = map(unicode, kurt_list.items), - ) + name=name, + list_items=map( + unicode, kurt_list.items), + ) pos = kurt_list.watcher.pos if pos: @@ -455,7 +457,7 @@ def save_lists(self, kurt_target, v14_morph): if not kurt_list.watcher.is_visible: x += 534 y += 71 - v14_list.bounds = Rectangle([x, y, x+95, y+115]) + v14_list.bounds = Rectangle([x, y, x + 95, y + 115]) v14_list.target = v14_morph if kurt_list.watcher.is_visible: @@ -496,11 +498,13 @@ def load_watcher(self, v14_watcher): def save_watcher(self, kurt_watcher): v14_watcher = self.UserObject("WatcherMorph") readout = v14_watcher.readout = self.UserObject("UpdatingStringMorph", - font_with_size = [Symbol('VerdanaBold'), 10], - ) + font_with_size=[ + Symbol('VerdanaBold'), 10], + ) v14_watcher.readoutFrame = self.UserObject("WatcherReadoutFrameMorph", - submorphs = [v14_watcher.readout] - ) + submorphs=[ + v14_watcher.readout] + ) if kurt_watcher.pos: (x, y) = kurt_watcher.pos @@ -535,7 +539,7 @@ def save_watcher(self, kurt_watcher): v14_watcher.sliderMin = kurt_watcher.slider_min v14_watcher.sliderMax = kurt_watcher.slider_max - v14_watcher.bounds = Rectangle([x, y, x+1, x+1]) + v14_watcher.bounds = Rectangle([x, y, x + 1, x + 1]) return v14_watcher @@ -644,7 +648,7 @@ def grab_comments(block): images = map(self.save_image, kurt_scriptable.costumes) v14_scriptable.media = OrderedCollection( - images + map(self.save_sound, kurt_scriptable.sounds)) + images + map(self.save_sound, kurt_scriptable.sounds)) v14_scriptable.costume = images[kurt_scriptable.costume_index] @@ -657,7 +661,7 @@ def grab_comments(block): v14_scriptable.name = kurt_scriptable.name v14_scriptable.rotationDegrees = kurt_scriptable.direction - 90 v14_scriptable.rotationStyle = Symbol( - kurt_scriptable.rotation_style) + kurt_scriptable.rotation_style) v14_scriptable.draggable = kurt_scriptable.is_draggable v14_scriptable.flags = 0 if kurt_scriptable.is_visible else 1 @@ -667,7 +671,7 @@ def grab_comments(block): x = x + 240 - rx y = 180 - y - ry (w, h) = kurt_scriptable.costume.image.size - v14_scriptable.bounds = Rectangle([x, y, x+w, y+h]) + v14_scriptable.bounds = Rectangle([x, y, x + w, y + h]) class Scratch14Plugin(KurtPlugin): @@ -690,14 +694,13 @@ def save(self, fp, project): Kurt.register(Scratch14Plugin()) - #-- Block workarounds --# # 1.4 -> 2.0 block_workaround('stop script', kurt.Block('stop', 'this script')) block_workaround('stop all', kurt.Block('stop', 'all')) block_workaround('forever if', - lambda block: kurt.Block('forever', [kurt.Block('if', *block.args)])) + lambda block: kurt.Block('forever', [kurt.Block('if', *block.args)])) block_workaround('loud?', kurt.Block('>', kurt.Block('loudness'), 30)) # 2.0 -> 1.4 @@ -705,4 +708,3 @@ def save(self, fp, project): 'this script': kurt.Block('stop script'), 'all': kurt.Block('stop all'), }.get(block.args[0], None)) - diff --git a/kurt/scratch14/blocks.py b/kurt/scratch14/blocks.py index ef027ea..2e7d61b 100644 --- a/kurt/scratch14/blocks.py +++ b/kurt/scratch14/blocks.py @@ -26,19 +26,19 @@ from kurt.scratch14.blockspecs_src import * - #-- Squeak blockspecs parser --# TOKENS = map(re.compile, [ r"'()'", r"'(.*?[^\\])?'", r'#[~-]', - r'#([A-Za-z+*/\\<>=&|~:-]+)', # _=@%?!`^$ + r'#([A-Za-z+*/\\<>=&|~:-]+)', # _=@%?!`^$ r'(-?[0-9]+(\.[0-9]+)?)', r'\(', r'\)', ]) + def tokenize(squeak_code): remain = str(squeak_code) while remain: @@ -57,6 +57,7 @@ def tokenize(squeak_code): else: raise SyntaxError, "Unknown token at %r" % remain + def parse(squeak_code): tokens = tokenize(squeak_code) for token in tokens: @@ -77,6 +78,7 @@ def parse(squeak_code): else: yield token + def make_blocks(squeak_code): category = None for thing in parse(squeak_code): @@ -91,7 +93,6 @@ def make_blocks(squeak_code): yield block - #-- convert to kurt blocks --# CATEGORIES = set([ @@ -123,7 +124,7 @@ def make_blocks(squeak_code): 'M': 'hat', 'S': 'hat', 's': 'stack', - 't': 'stack', # timed blocks, all stack + 't': 'stack', # timed blocks, all stack } INSERT_SHAPES = { @@ -134,35 +135,35 @@ def make_blocks(squeak_code): '%c': 'color', # color picker with menu [#hexcode] '%C': 'color', # color [#hexcode] - '%m': 'readonly-menu', # morph reference [Sprite1 v] - '%a': 'readonly-menu', # attribute of sprite [x position v] - '%e': 'readonly-menu', # broadcast message [Play v] - '%k': 'readonly-menu', # key [space v] + '%m': 'readonly-menu', # morph reference [Sprite1 v] + '%a': 'readonly-menu', # attribute of sprite [x position v] + '%e': 'readonly-menu', # broadcast message [Play v] + '%k': 'readonly-menu', # key [space v] - '%v': 'readonly-menu', # variable [var v] - '%L': 'readonly-menu', # list name [list v] + '%v': 'readonly-menu', # variable [var v] + '%L': 'readonly-menu', # list name [list v] '%i': 'number-menu', # item ( ) 1/last/any '%y': 'number-menu', # delete line %y of list ( ) 1/last/all - '%f': 'readonly-menu', # math function [sqrt v] + '%f': 'readonly-menu', # math function [sqrt v] - '%l': 'readonly-menu', # costume name [Costume1 v] - '%g': 'readonly-menu', # graphic effect menu [ghost v] + '%l': 'readonly-menu', # costume name [Costume1 v] + '%g': 'readonly-menu', # graphic effect menu [ghost v] - '%S': 'readonly-menu', # sound selector [meow v] + '%S': 'readonly-menu', # sound selector [meow v] '%D': 'number-menu', # MIDI drum (48 v) '%N': 'number-menu', # MIDI note (60 v) '%I': 'number-menu', # MIDI instrument (1 v) - '%h': 'readonly-menu', # Boolean sensor board selector menu - '%H': 'readonly-menu', # Numerical sensor board selector menu - '%W': 'readonly-menu', # motor direction + '%h': 'readonly-menu', # Boolean sensor board selector menu + '%H': 'readonly-menu', # Numerical sensor board selector menu + '%W': 'readonly-menu', # motor direction } INSERT_KINDS = { '%d': 'direction', - '%m': 'spriteOrMouse', # most of the time + '%m': 'spriteOrMouse', # most of the time '%a': 'attribute', '%e': 'broadcast', '%k': 'key', @@ -200,7 +201,7 @@ def make_blocks(squeak_code): MATCH_COMMANDS = { 'KeyEventHatMorph': 'whenKeyPressed', 'drum:duration:elapsed:from:': 'playDrum', - '\\\\': '%', # modulo + '\\\\': '%', # modulo 'midiInstrument:': 'instrument:', 'nextBackground': 'nextScene', 'showBackground:': 'startScene', @@ -208,6 +209,7 @@ def make_blocks(squeak_code): INSERT_RE = re.compile(r'(%.(?:\.[A-z]+)?)') + def blockify(category, text, flag, command, defaults): if command in IGNORE_COMMANDS: return @@ -237,7 +239,7 @@ def blockify(category, text, flag, command, defaults): parts += [kurt.Insert("stack")] pbt = kurt.PluginBlockType(category, shape, command, parts, - match=match) + match=match) # fix insert kinds if command == 'getAttribute:of:': @@ -254,36 +256,35 @@ def blockify(category, text, flag, command, defaults): return pbt - #-- build lists --# block_list = (list(make_blocks(squeak_blockspecs)) + - list(make_blocks(squeak_stage_blockspecs)) + - list(make_blocks(squeak_sprite_blockspecs)) + - list(make_blocks(squeak_obsolete_blockspecs))) + list(make_blocks(squeak_stage_blockspecs)) + + list(make_blocks(squeak_sprite_blockspecs)) + + list(make_blocks(squeak_obsolete_blockspecs))) block_list += [ # variable reporters kurt.PluginBlockType('variables', 'reporter', 'readVariable', - [kurt.Insert('inline', 'var', default='var')]), + [kurt.Insert('inline', 'var', default='var')]), kurt.PluginBlockType('variables', 'reporter', 'contentsOfList:', - [kurt.Insert('inline', 'list', default='list')]), + [kurt.Insert('inline', 'list', default='list')]), # Blocks with different meaning depending on arguments are special-cased # inside load_block/save_block. kurt.PluginBlockType('control', 'hat', 'whenGreenFlag', - ['when green flag clicked']), + ['when green flag clicked']), kurt.PluginBlockType('control', 'hat', 'whenIReceive', - ['when I receive ', kurt.Insert('readonly-menu', 'broadcast')]), + ['when I receive ', kurt.Insert('readonly-menu', 'broadcast')]), # changeVariable is special-cased (and isn't in blockspecs) kurt.PluginBlockType('variables', 'stack', 'changeVar:by:', ['change ', - kurt.Insert('readonly-menu', 'var'), ' by ', kurt.Insert('number')]), + kurt.Insert('readonly-menu', 'var'), ' by ', kurt.Insert('number')]), kurt.PluginBlockType('variables', 'stack', 'setVar:to:', ['set ', - kurt.Insert('readonly-menu', 'var'), ' to ', kurt.Insert('string')]), + kurt.Insert('readonly-menu', 'var'), ' to ', kurt.Insert('string')]), # MouseClickEventHatMorph is special-cased as it has an extra argument: # 'when %m clicked' kurt.PluginBlockType('control', 'hat', 'whenClicked', - ['when clicked']), + ['when clicked']), ] diff --git a/kurt/scratch14/blockspecs_src.py b/kurt/scratch14/blockspecs_src.py index b856fd6..13abedd 100644 --- a/kurt/scratch14/blockspecs_src.py +++ b/kurt/scratch14/blockspecs_src.py @@ -9,4 +9,3 @@ squeak_sprite_blockspecs = """'motion' ('move %n steps' #- #forward:) ('turn cw %n degrees' #- #turnRight: 15) ('turn ccw %n degrees' #- #turnLeft: 15) #- ('point in direction %d' #- #heading: 90) ('point towards %m' #- #pointTowards:) #- ('go to x:%n y:%n' #- #gotoX:y: 0 0) ('go to %m' #- #gotoSpriteOrMouse:) ('glide %n secs to x:%n y:%n' #t #glideSecs:toX:y:elapsed:from: 1 50 50) #- ('change x by %n' #- #changeXposBy: 10) ('set x to %n' #- #xpos: 0) ('change y by %n' #- #changeYposBy: 10) ('set y to %n' #- #ypos: 0) #- ('if on edge, bounce' #- #bounceOffEdge) #- ('x position' #r #xpos) ('y position' #r #ypos) ('direction' #r #heading) 'pen' ('clear' #- #clearPenTrails) #- ('pen down' #- #putPenDown) ('pen up' #- #putPenUp) #- ('set pen color to %c' #- #penColor:) ('change pen color by %n' #- #changePenHueBy:) ('set pen color to %n' #- #setPenHueTo: 0) #- ('change pen shade by %n' #- #changePenShadeBy:) ('set pen shade to %n' #- #setPenShadeTo: 50) #- ('change pen size by %n' #- #changePenSizeBy: 1) ('set pen size to %n' #- #penSize: 1) #- ('stamp' #- #stampCostume) 'looks' ('switch to costume %l' #- #lookLike:) ('next costume' #- #nextCostume) ('costume #' #r #costumeIndex) #- ('say %s for %n secs' #t #say:duration:elapsed:from: 'Hello!' 2) ('say %s' #- #say: 'Hello!') ('think %s for %n secs' #t #think:duration:elapsed:from: 'Hmm...' 2) ('think %s' #- #think: 'Hmm...') #- ('change %g effect by %n' #- #changeGraphicEffect:by: 'color' 25) ('set %g effect to %n' #- #setGraphicEffect:to: 'color' 0) ('clear graphic effects' #- #filterReset) #- ('change size by %n' #- #changeSizeBy:) ('set size to %n%' #- #setSizeTo: 100) ('size' #r #scale) #- ('show' #- #show) ('hide' #- #hide) #- ('go to front' #- #comeToFront) ('go back %n layers' #- #goBackByLayers: 1) 'sensing' ('touching %m?' #b #touching:) ('touching color %C?' #b #touchingColor:) ('color %C is touching %C?' #b #color:sees:) #- ('ask %s and wait' #s #doAsk 'What''s your name?') ('answer' #r #answer) #- ('mouse x' #r #mouseX) ('mouse y' #r #mouseY) ('mouse down?' #b #mousePressed) #- ('key %k pressed?' #b #keyPressed: 'space') #- ('distance to %m' #r #distanceTo:) #- ('reset timer' #- #timerReset) ('timer' #r #timer) #- ('%a of %m' #r #getAttribute:of:) #- ('loudness' #r #soundLevel) ('loud?' #b #isLoud) #~ ('%H sensor value' #r #sensor: 'slider') ('sensor %h?' #b #sensorPressed: 'button pressed')""" squeak_obsolete_blockspecs = """'obsolete number blocks' ('abs %n' #r #abs #-) ('sqrt %n' #r #sqrt #-) 'obsolete sound blocks' ('rewind sound %S' #- #rewindSound:) 'obsolete sprite motion blocks' ('point away from edge' #- #turnAwayFromEdge) ('glide x:%n y:%n in %n secs' #t #gotoX:y:duration:elapsed:from: 50 50 1) 'obsolete sprite looks blocks' ('change costume by %n' #- #changeCostumeIndexBy: 1) ('change background by %n' #- #changeBackgroundIndexBy: 1) #- ('change stretch by %n' #- #changeStretchBy:) ('set stretch to %n%' #- #setStretchTo: 100) #- ('say nothing' #- #sayNothing) #- ('change visibility by %n' #- #changeVisibilityBy: -10) ('set visibility to %n%' #- #setVisibilityTo: 100) 'obsolete image effects' ('change color-effect by %n' #- #changeHueShiftBy: 25) ('set color-effect to %n' #- #setHueShiftTo: 0) #- ('change fisheye by %n' #- #changeFisheyeBy: 10) ('set fisheye to %n' #- #setFisheyeTo: 0) #~ ('change whirl by %n' #- #changeWhirlBy: 30) ('set whirl to %n' #- #setWhirlTo: 0) #- ('change pixelate by %n' #- #changePixelateCountBy: 1) ('set pixelate to %n' #- #setPixelateCountTo: 1) #~ ('change mosaic by %n' #- #changeMosaicCountBy: 1) ('set mosaic to %n' #- #setMosaicCountTo: 1) #- ('change brightness-shift by %n' #- #changeBrightnessShiftBy: 10) ('set brightness-shift to %n' #- #setBrightnessShiftTo: 0) #~ ('change saturation-shift by %n' #- #changeSaturationShiftBy: 10) ('set saturation-shift to %n' #- #setSaturationShiftTo: 0) #- ('change pointillize drop by %n' #- #changePointillizeSizeBy: 5) ('set pointillize drop to %n' #- #setPointillizeSizeTo: 0) #~ ('change water ripple by %n' #- #changeWaterRippleBy: 5) ('set water ripple to %n' #- #setWaterRippleTo: 0) #- ('change blur by %n' #- #changeBlurBy: 1) ('set blur to %n' #- #setBlurTo: 0)""" - diff --git a/kurt/scratch14/fixed_objects.py b/kurt/scratch14/fixed_objects.py index 8b91242..52841bb 100644 --- a/kurt/scratch14/fixed_objects.py +++ b/kurt/scratch14/fixed_objects.py @@ -19,7 +19,7 @@ import PIL -from array import array # used by Form +from array import array # used by Form from copy import copy import itertools import operator @@ -76,6 +76,7 @@ def default_colormap(): class FixedObject(object): """A primitive fixed-format object - eg String, Dictionary. value property - contains the object's value.""" + def __init__(self, value): if isinstance(value, FixedObject): value = value.value @@ -83,8 +84,8 @@ def __init__(self, value): def to_construct(self, context): return Container( - classID = self.__class__.__name__, - value = self.to_value(), + classID=self.__class__.__name__, + value=self.to_value(), ) @classmethod @@ -117,7 +118,9 @@ def copy(self): __copy__ = copy -class ContainsRefs: pass +class ContainsRefs: + pass + class FixedObjectWithRepeater(FixedObject): """Used internally to handle things like @@ -126,8 +129,9 @@ class FixedObjectWithRepeater(FixedObject): MetaRepeater(lambda ctx: ctx.length, UBInt32("items")), ) """ + def to_value(self): - return Container(items = self.value, length = len(self.value)) + return Container(items=self.value, length=len(self.value)) @classmethod def from_value(cls, obj): @@ -136,6 +140,7 @@ def from_value(cls, obj): class FixedObjectByteArray(FixedObject): + def __repr__(self): name = self.__class__.__name__ value = repr(self.value) @@ -147,6 +152,7 @@ def __repr__(self): # Bytes + class String(FixedObjectByteArray): classID = 9 _construct = PascalString("value", length_field=UBInt32("length")) @@ -155,6 +161,7 @@ class String(FixedObjectByteArray): class Symbol(FixedObjectByteArray): classID = 10 _construct = PascalString("value", length_field=UBInt32("length")) + def __repr__(self): return "Symbol(%r)" % self.value @@ -170,9 +177,9 @@ def __repr__(self): class SoundBuffer(FixedObjectByteArray): classID = 12 _construct = Struct("", - UBInt32("length"), - construct.String("items", lambda ctx: ctx.length * 2), - ) + UBInt32("length"), + construct.String("items", lambda ctx: ctx.length * 2), + ) @classmethod def from_value(cls, obj): @@ -181,8 +188,8 @@ def from_value(cls, obj): def to_value(self): value = self.value length = (len(value) + 3) / 2 - value += "\x00" * (length * 2 - len(value)) # padding - return Container(items = value, length = length) + value += "\x00" * (length * 2 - len(value)) # padding + return Container(items=value, length=length) # Bitmap 13 - found later in file @@ -193,15 +200,14 @@ class UTF8(FixedObjectByteArray): encoding="utf8") - - # Collections class Collection(FixedObjectWithRepeater, ContainsRefs): _construct = Struct("", - UBInt32("length"), - MetaRepeater(lambda ctx: ctx.length, Rename("items", field)), - ) + UBInt32("length"), + MetaRepeater( + lambda ctx: ctx.length, Rename("items", field)), + ) def __init__(self, value=None): if value == None: @@ -232,31 +238,40 @@ def __len__(self): def copy(self): return self.__class__(list(self.value.copy)) + class Array(Collection): classID = 20 + + class OrderedCollection(Collection): classID = 21 + + class Set(Collection): classID = 22 + + class IdentitySet(Collection): classID = 23 - # Dictionary class Dictionary(Collection): classID = 24 _construct = Struct("dictionary", - UBInt32("length"), - MetaRepeater(lambda ctx: ctx.length, Struct("items", - Rename("key", field), - Rename("value", field), - )), - ) + UBInt32("length"), + MetaRepeater(lambda ctx: ctx.length, Struct("items", + Rename( + "key", field), + Rename( + "value", field), + )), + ) def __init__(self, value=None): - if value == None: value = {} + if value == None: + value = {} Collection.__init__(self, value) def to_value(self): @@ -277,11 +292,11 @@ def __getattr__(self, name): def copy(self): return self.__class__(self.value.copy()) + class IdentityDictionary(Dictionary): classID = 25 - # Color class Color(FixedObject): @@ -292,18 +307,18 @@ class Color(FixedObject): """ classID = 30 _construct = BitStruct("value", - Padding(2), - Bits("r", 10), - Bits("g", 10), - Bits("b", 10), - ) + Padding(2), + Bits("r", 10), + Bits("g", 10), + Bits("b", 10), + ) _construct_32_rgba = Struct("", - UBInt8("r"), - UBInt8("g"), - UBInt8("b"), - UBInt8("alpha"), - ) + UBInt8("r"), + UBInt8("g"), + UBInt8("b"), + UBInt8("alpha"), + ) def __init__(self, value): self.value = value @@ -353,19 +368,18 @@ def to_argb_array(self): return bytearray((255, r, g, b)) - class TranslucentColor(Color): classID = 31 _construct = Struct("", - Embed(Color._construct), - UBInt8("alpha"), # I think. - ) + Embed(Color._construct), + UBInt8("alpha"), # I think. + ) _construct_32 = Struct("", - UBInt8("alpha"), - UBInt8("r"), - UBInt8("g"), - UBInt8("b"), - ) + UBInt8("alpha"), + UBInt8("r"), + UBInt8("g"), + UBInt8("b"), + ) def __init__(self, value): self.value = value @@ -395,20 +409,18 @@ def to_argb_array(self): return bytearray((a, r, g, b)) - - - # Dimensions class Point(FixedObject): classID = 32 _construct = Struct("", - Rename("x", field), - Rename("y", field), - ) + Rename("x", field), + Rename("y", field), + ) def __init__(self, x, y=None): - if y is None: (x, y) = x + if y is None: + (x, y) = x self.x = x self.y = y @@ -423,12 +435,13 @@ def __repr__(self): return 'Point(%r, %r)' % self.value def to_value(self): - return Container(x = self.x, y = self.y) + return Container(x=self.x, y=self.y) @classmethod def from_value(cls, value): return cls(value.x, value.y) + class Rectangle(FixedObject): classID = 33 _construct = StrictRepeater(4, field) @@ -439,8 +452,6 @@ def from_value(cls, value): return cls(value) - - # Form/images def get_run_length(ctx): @@ -449,13 +460,14 @@ def get_run_length(ctx): except AttributeError: return ctx._.run_length + class Bitmap(FixedObjectByteArray): classID = 13 _construct = Struct("", - UBInt32("length"), - construct.String("items", lambda ctx: ctx.length * 4), - # Identically named "String" class -_- - ) + UBInt32("length"), + construct.String("items", lambda ctx: ctx.length * 4), + # Identically named "String" class -_- + ) @classmethod def from_value(cls, obj): @@ -464,58 +476,66 @@ def from_value(cls, obj): def to_value(self): value = self.value length = (len(value) + 3) / 4 - value += "\x00" * (length * 4 - len(value)) # padding - return Container(items = value, length = length) + value += "\x00" * (length * 4 - len(value)) # padding + return Container(items=value, length=length) _int = Struct("int", - UBInt8("_value"), - If(lambda ctx: ctx._value > 223, - IfThenElse("", lambda ctx: ctx._value <= 254, Embed(Struct("", - UBInt8("_second_byte"), - Value("_value", - lambda ctx: (ctx._value - 224) * 256 + ctx._second_byte), - )), Embed(Struct("", - UBInt32("_value"), - ))) - ), - ) + UBInt8("_value"), + If(lambda ctx: ctx._value > 223, + IfThenElse("", lambda ctx: ctx._value <= 254, Embed(Struct("", + UBInt8( + "_second_byte"), + Value("_value", + lambda ctx: (ctx._value - 224) * 256 + ctx._second_byte), + )), Embed(Struct("", + UBInt32( + "_value"), + ))) + ), + ) _length_run_coding = Struct("", - Embed(_int), #ERROR? - Value("length", lambda ctx: ctx._value), - - OptionalGreedyRepeater( - Struct("data", - Embed(_int), - Value("data_code", lambda ctx: ctx._value % 4), - Value("run_length", lambda ctx: - (ctx._value - ctx.data_code) / 4), - Switch("", lambda ctx: ctx.data_code, { - 0: Embed(Struct("", - StrictRepeater(get_run_length, - Value("pixels", lambda ctx: "\x00\x00\x00\x00") - ), - )), - 1: Embed(Struct("", Bytes("_b", 1), - StrictRepeater(get_run_length, - Value("pixels", lambda ctx: ctx._b * 4), - ), - )), - 2: Embed(Struct("", - Bytes("_pixel", 4), - StrictRepeater(get_run_length, - Value("pixels", lambda ctx: ctx._pixel), - ), - )), - 3: Embed(Struct("", - StrictRepeater(get_run_length, - Bytes("pixels", 4), - ), - )), - }), - ) - ) - ) + Embed(_int), # ERROR? + Value("length", lambda ctx: ctx._value), + + OptionalGreedyRepeater( + Struct("data", + Embed(_int), + Value( + "data_code", lambda ctx: ctx._value % 4), + Value("run_length", lambda ctx: + (ctx._value - ctx.data_code) / 4), + Switch("", lambda ctx: ctx.data_code, { + 0: Embed(Struct("", + StrictRepeater(get_run_length, + Value( + "pixels", lambda ctx: "\x00\x00\x00\x00") + ), + )), + 1: Embed(Struct("", Bytes("_b", 1), + StrictRepeater(get_run_length, + Value( + "pixels", lambda ctx: ctx._b * 4), + ), + )), + 2: Embed(Struct("", + Bytes( + "_pixel", 4), + StrictRepeater(get_run_length, + Value( + "pixels", lambda ctx: ctx._pixel), + ), + )), + 3: Embed(Struct("", + StrictRepeater(get_run_length, + Bytes( + "pixels", 4), + ), + )), + }), + ) + ) + ) @classmethod def from_byte_array(cls, bytes_): @@ -528,13 +548,11 @@ def from_byte_array(cls, bytes_): data = "".join(itertools.chain.from_iterable(pixels)) return cls(data) - def compress(self): """Compress to a ByteArray""" raise NotImplementedError - class Form(FixedObject, ContainsRefs): """A rectangular array of pixels, used for holding images. Attributes: @@ -548,12 +566,12 @@ class Form(FixedObject, ContainsRefs): classID = 34 _construct = Struct("form", - Rename("width", field), - Rename("height", field), - Rename("depth", field), - Rename("privateOffset", field), - Rename("bits", field), # Bitmap - ) + Rename("width", field), + Rename("height", field), + Rename("depth", field), + Rename("privateOffset", field), + Rename("bits", field), # Bitmap + ) def __init__(self, **fields): self.width = 0 @@ -612,12 +630,12 @@ def to_array(self): # This is way faster than doing different bit-shifting ops to get # each pixel depending on depth. - multicolors = itertools.product(colors, repeat=8//self.depth) + multicolors = itertools.product(colors, repeat=8 // self.depth) multicolors = [bytearray(b'').join(multicolor) for multicolor in multicolors] wide_argb_array = bytearray(b'').join( - operator.itemgetter(*pixel_bytes)(multicolors)) + operator.itemgetter(*pixel_bytes)(multicolors)) # Rows are rounded to be a whole number of words (32 bits) long. # Presumably this is because Bitmaps are compressed (run-length @@ -627,12 +645,12 @@ def to_array(self): skip = (pixels_per_word - pixels_in_last_word) % pixels_per_word out_rowlen = self.width * 4 - in_rowlen = out_rowlen + skip * 4 + in_rowlen = out_rowlen + skip * 4 row_indexes = xrange(0, len(wide_argb_array), in_rowlen) - argb_array = bytearray(b'').join(wide_argb_array[i:i+out_rowlen] - for i in row_indexes) + argb_array = bytearray(b'').join(wide_argb_array[i:i + out_rowlen] + for i in row_indexes) else: - raise NotImplementedError # TODO: depth 16 + raise NotImplementedError # TODO: depth 16 size = (self.width, self.height) return PIL.Image.frombuffer("RGBA", size, buffer(argb_array), "raw", @@ -646,18 +664,19 @@ def from_string(cls, width, height, rgba_string): # Convert RGBA string to ARGB raw = "" for i in range(0, len(rgba_string), 4): - raw += rgba_string[i+3] # alpha - raw += rgba_string[i:i+3] # rgb + raw += rgba_string[i + 3] # alpha + raw += rgba_string[i:i + 3] # rgb assert len(rgba_string) == width * height * 4 return Form( - width = width, - height = height, - depth = 32, - bits = Bitmap(raw), + width=width, + height=height, + depth=32, + bits=Bitmap(raw), ) + class ColorForm(Form): """A rectangular array of pixels, used for holding images. width, height - dimensions @@ -668,13 +687,6 @@ class ColorForm(Form): """ classID = 35 _construct = Struct("", - Embed(Form._construct), - Rename("colors", field), # Array - ) - - - - - - - + Embed(Form._construct), + Rename("colors", field), # Array + ) diff --git a/kurt/scratch14/heights.py b/kurt/scratch14/heights.py index 5b1b26e..c2efc4b 100644 --- a/kurt/scratch14/heights.py +++ b/kurt/scratch14/heights.py @@ -27,7 +27,6 @@ import kurt - def block_height(block): command = block.type.convert("scratch14").command @@ -35,7 +34,7 @@ def block_height(block): 'KeyEventHatMorph': 41, 'whenGreenFlag': 43, 'whenIReceive': 39, - 'whenClicked': 38, # MouseClickEventHatMorph + 'whenClicked': 38, # MouseClickEventHatMorph 'stopAll': 22, } @@ -51,7 +50,7 @@ def block_height(block): if block.type.has_insert('readonly-menu'): height += 2 elif block.type.has_insert('number') or \ - block.type.has_insert('string'): + block.type.has_insert('string'): height += 1 return height @@ -120,4 +119,3 @@ def clean_up(scripts): elif isinstance(script, kurt.Comment): y += 14 y += 15 - diff --git a/kurt/scratch14/inline_objects.py b/kurt/scratch14/inline_objects.py index c7d4f0b..1999cf5 100644 --- a/kurt/scratch14/inline_objects.py +++ b/kurt/scratch14/inline_objects.py @@ -22,7 +22,6 @@ # We can't import the name Array, as we use it. -_- - ### Inline fields & Refs ### class Ref(object): @@ -34,6 +33,7 @@ class Ref(object): Found in UserObjects and certain FixedObjects. """ + def __init__(self, index): """Initialise a reference. @param index: the index in the object table that the reference points to @@ -44,12 +44,12 @@ def __init__(self, index): def to_construct(self): #index1 = self.index % 65536 #index2 = (self.index - index1) >> 16 - #return Container(classID = 'Ref', _index1 = index1, _index2 = index2) + # return Container(classID = 'Ref', _index1 = index1, _index2 = index2) return Container(classID="Ref", index=self.index) @classmethod def from_construct(cls, obj): - index = obj.index #int(obj._index2 << 16) + obj._index1 + index = obj.index # int(obj._index2 << 16) + obj._index1 return Ref(index) def __repr__(self): @@ -66,6 +66,7 @@ def __hash__(self): class RefAdapter(Adapter): + def _encode(self, obj, context): assert isinstance(obj, Ref) return obj.to_construct() @@ -75,6 +76,7 @@ def _decode(self, obj, context): class LargeIntegerAdapter(Adapter): + def __init__(self, sign, *args, **kwargs): self.sign = sign Adapter.__init__(self, *args, **kwargs) @@ -97,6 +99,7 @@ def _encode(self, obj, context): class FieldAdapter(Adapter): + def _encode(self, obj, context): assert not isinstance(obj, str) @@ -130,7 +133,6 @@ def _decode(self, obj, context): return obj.value - """Construct for simple inline field values and references. They are encoded inline and not stored as object table entries. They do not @@ -138,35 +140,39 @@ def _decode(self, obj, context): """ field = FieldAdapter(Struct("field", - Enum(UBInt8("classID"), - nil = 1, - true = 2, - false = 3, - SmallInteger = 4, - SmallInteger16 = 5, - LargePositiveInteger = 6, - LargeNegativeInteger = 7, - Float = 8, - Ref = 99, - ), - Switch("value", lambda ctx: ctx.classID, { - "nil": Value("", lambda ctx: None), - "true": Value("", lambda ctx: True), - "false": Value("", lambda ctx: False), - "SmallInteger": SBInt32(""), - "SmallInteger16": SBInt16(""), - "LargePositiveInteger": LargeIntegerAdapter('+', Struct("", - UBInt16("length"), - MetaRepeater(lambda ctx: ctx.length, UBInt8("data")), - )), - "LargeNegativeInteger": LargeIntegerAdapter('-', Struct("", - UBInt16("length"), - MetaRepeater(lambda ctx: ctx.length, UBInt8("data")), - )), - "Float": BFloat64(""), - "Ref": RefAdapter(BitStruct("", - BitField("index", 24), - )), - }) -)) - + Enum(UBInt8("classID"), + nil=1, + true=2, + false=3, + SmallInteger=4, + SmallInteger16=5, + LargePositiveInteger=6, + LargeNegativeInteger=7, + Float=8, + Ref=99, + ), + Switch("value", lambda ctx: ctx.classID, { + "nil": Value("", lambda ctx: None), + "true": Value("", lambda ctx: True), + "false": Value("", lambda ctx: False), + "SmallInteger": SBInt32(""), + "SmallInteger16": SBInt16(""), + "LargePositiveInteger": LargeIntegerAdapter('+', Struct("", + UBInt16( + "length"), + MetaRepeater( + lambda ctx: ctx.length, UBInt8("data")), + )), + "LargeNegativeInteger": LargeIntegerAdapter('-', Struct("", + UBInt16( + "length"), + MetaRepeater( + lambda ctx: ctx.length, UBInt8("data")), + )), + "Float": BFloat64(""), + "Ref": RefAdapter(BitStruct("", + BitField( + "index", 24), + )), + }) + )) diff --git a/kurt/scratch14/objtable.py b/kurt/scratch14/objtable.py index b9420df..9c20339 100644 --- a/kurt/scratch14/objtable.py +++ b/kurt/scratch14/objtable.py @@ -27,9 +27,9 @@ from functools import partial import inspect -from inline_objects import field, Ref from fixed_objects import * import fixed_objects +from inline_objects import field, Ref from user_objects import * @@ -38,6 +38,7 @@ class ObjectAdapter(Adapter): The class must have a from_construct classmethod and a to_construct instancemethod. """ + def __init__(self, classes, *args, **kwargs): """Initialize an adapter for a new type/object(s). @param classes: class, list of classes, or dict of obj.class name to @@ -76,7 +77,6 @@ def _decode(self, obj, context): return cls.from_construct(obj, context) - #-- Get object classes --# def obj_classes_from_module(module): @@ -106,9 +106,11 @@ def obj_classes_from_module(module): """ fixed_object = FixedObjectAdapter(Struct("fixed_object", - Enum(UBInt8("classID"), **fixed_object_ids_by_name), - Switch("value", lambda ctx: ctx.classID, fixed_object_cons_by_name), -)) + Enum( + UBInt8("classID"), **fixed_object_ids_by_name), + Switch( + "value", lambda ctx: ctx.classID, fixed_object_cons_by_name), + )) # User-class objects @@ -116,13 +118,14 @@ def obj_classes_from_module(module): in user_object_class_ids.items()) uo_struct = Struct("user_object", - Enum(UBInt8("classID"), - **user_object_ids_by_name - ), - UBInt8("version"), - UBInt8("length"), - Rename("values", MetaRepeater(lambda ctx: ctx.length, field)), -) + Enum(UBInt8("classID"), + **user_object_ids_by_name + ), + UBInt8("version"), + UBInt8("length"), + Rename( + "values", MetaRepeater(lambda ctx: ctx.length, field)), + ) """Construct for UserObjects. @@ -131,7 +134,6 @@ def obj_classes_from_module(module): user_object = uo_struct - #-- Object Table --# class PythonicAdapter(Adapter): @@ -141,6 +143,7 @@ class PythonicAdapter(Adapter): * Dictionary -- dict * Array -- list/tuple """ + def _encode(self, obj, context): if isinstance(obj, str): return String(obj) @@ -165,7 +168,9 @@ def _decode(self, obj, context): else: return obj + class ObjectAdapter(Adapter): + def _encode(self, obj, context): classID = getattr(obj, 'classID', getattr(obj, 'classID')) if classID in fixed_object_ids_by_name: @@ -173,8 +178,8 @@ def _encode(self, obj, context): elif classID in user_object_ids_by_name: classID = user_object_ids_by_name[classID] return Container( - classID = classID, - object = obj, + classID=classID, + object=obj, ) def _decode(self, obj, context): @@ -183,21 +188,23 @@ def _decode(self, obj, context): return obj obj_table_entry = PythonicAdapter(ObjectAdapter(Struct("object", - Peek(UBInt8("classID")), - IfThenElse("object", lambda ctx: ctx.classID < 99, - fixed_object, - user_object, - ), -))) + Peek(UBInt8("classID")), + IfThenElse("object", lambda ctx: ctx.classID < 99, + fixed_object, + user_object, + ), + ))) obj_table_entry.__doc__ = """Construct for object table entries, both UserObjects and FixedObjects.""" + class ObjectTableAdapter(Adapter): + def _encode(self, objects, context): return Container( - header = "ObjS\x01Stch\x01", - length = len(objects), - objects = objects, + header="ObjS\x01Stch\x01", + length=len(objects), + objects=objects, ) def _decode(self, table, context): @@ -210,10 +217,13 @@ def _decode(self, table, context): """ obj_table = ObjectTableAdapter(Struct("object_table", - Const(Bytes("header", 10), "ObjS\x01Stch\x01"), - UBInt32("length"), - Rename("objects", MetaRepeater(lambda ctx: ctx.length, obj_table_entry)), -)) + Const( + Bytes("header", 10), "ObjS\x01Stch\x01"), + UBInt32("length"), + Rename( + "objects", MetaRepeater(lambda ctx: ctx.length, obj_table_entry)), + )) + class InfoTableAdapter(Subconstruct): """Info ObjTable found in the project header. @@ -239,11 +249,10 @@ def _build(self, obj, stream, context): info_table = InfoTableAdapter(obj_table) scratch_file = Struct("scratch_file", - Literal("ScratchV02"), - Rename("info", info_table), - Rename("stage", obj_table), -) - + Literal("ScratchV02"), + Rename("info", info_table), + Rename("stage", obj_table), + ) #-- object network to/from table --# @@ -258,7 +267,7 @@ def resolve_ref(obj, objects=objects): return obj # Reading the ObjTable backwards somehow makes more sense. - for i in xrange(len(objects)-1, -1, -1): + for i in xrange(len(objects) - 1, -1, -1): obj = objects[i] if isinstance(obj, Container): @@ -297,6 +306,7 @@ def resolve_ref(obj, objects=objects): root = objects[0] return root + def encode_network(root): """Yield ref-containing obj table entries from object network""" orig_objects = [] @@ -318,11 +328,11 @@ def get_ref(value, objects=objects): objects.append(value) index = len(objects) value._tmp_index = index - orig_objects.append(value) # save the object so we can - # strip the _tmp_indexes later + orig_objects.append(value) # save the object so we can + # strip the _tmp_indexes later return Ref(index) else: - return value # Inline value + return value # Inline value def fix_fields(obj): obj = PythonicAdapter(Pass)._encode(obj, None) @@ -331,7 +341,7 @@ def fix_fields(obj): if isinstance(obj, Container): obj.update((k, get_ref(v)) for (k, v) in obj.items() - if k != 'class_name') + if k != 'class_name') fixed_obj = obj elif isinstance(obj, Dictionary): @@ -380,12 +390,13 @@ def fix_fields(obj): return objects + def encode_network(root): """Yield ref-containing obj table entries from object network""" def fix_values(obj): if isinstance(obj, Container): obj.update((k, get_ref(v)) for (k, v) in obj.items() - if k != 'class_name') + if k != 'class_name') fixed_obj = obj elif isinstance(obj, Dictionary): @@ -433,7 +444,7 @@ def get_ref(obj, objects=objects): objects[index - 1] = fix_values(obj) return Ref(index) else: - return obj # Inline value + return obj # Inline value get_ref(root) @@ -442,6 +453,7 @@ def get_ref(obj, objects=objects): del obj._index return objects + def decode_obj_table(table_entries, plugin): """Return root of obj table. Converts user-class objects""" entries = [] @@ -457,6 +469,7 @@ def decode_obj_table(table_entries, plugin): return decode_network(entries) + def encode_obj_table(root, plugin): """Return list of obj table entries. Converts user-class objects""" entries = encode_network(root) @@ -475,4 +488,3 @@ def encode_obj_table(root, plugin): values=attrs.values()) table_entries.append(entry) return table_entries - diff --git a/kurt/scratch14/user_objects.py b/kurt/scratch14/user_objects.py index 8fdd355..0feeae3 100644 --- a/kurt/scratch14/user_objects.py +++ b/kurt/scratch14/user_objects.py @@ -31,7 +31,6 @@ from kurt.scratch14.fixed_objects import * - #-- Class IDs --# user_object_class_ids = { @@ -105,15 +104,16 @@ } - #-- UserObject definition class --# class UserObjectDef(Container): + def __init__(self, version, inherits, defaults=[]): self.version = int(version) if version else None self.inherits = str(inherits) if inherits else None self.defaults = OrderedDict(defaults) + def make_user_objects(definitions): for obj in definitions.values(): all_parents = [obj] @@ -128,7 +128,6 @@ def make_user_objects(definitions): return definitions - #-- UserObject definitions --# user_objects_by_name = OrderedDict({ diff --git a/kurt/scratch20/__init__.py b/kurt/scratch20/__init__.py index 48caeac..62b2d94 100644 --- a/kurt/scratch20/__init__.py +++ b/kurt/scratch20/__init__.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2012 Tim Radvan # # This file is part of Kurt. @@ -25,18 +28,18 @@ import struct import kurt -from kurt.plugin import Kurt, KurtPlugin +from kurt.plugin import Kurt, KurtPlugin from kurt.scratch20.blocks import make_block_types, custom_block, make_spec SOUND_FORMATS = ['.wav'] WATCHER_MODES = [None, - 'normal', - 'large', - 'slider', -] + 'normal', + 'large', + 'slider', + ] CATEGORY_COLORS = { 'variables': kurt.Color('#ee7d16'), @@ -54,6 +57,7 @@ def get_blocks_by_id(this_block): yield b elif isinstance(this_block, kurt.Block): yield this_block + for arg in this_block.args: if isinstance(arg, kurt.Block): for block in get_blocks_by_id(arg): @@ -65,6 +69,7 @@ def get_blocks_by_id(this_block): class ZipReader(object): + def __init__(self, fp): self.zip_file = zipfile.ZipFile(fp, "r") self.json = json.load(self.zip_file.open("project.json")) @@ -77,10 +82,12 @@ def __init__(self, fp): # files self.image_filenames = {} self.sound_filenames = {} + for filename in self.zip_file.namelist(): if filename == 'project.json': continue (name, extension) = os.path.splitext(filename) + if extension in SOUND_FORMATS: self.sound_filenames[int(name)] = filename else: @@ -107,7 +114,8 @@ def __init__(self, fp): # watchers for actor in actors: if not isinstance(actor, kurt.Sprite): - if 'listName' in actor: continue + if 'listName' in actor: + continue actor = self.load_watcher(actor) self.project.actors.append(actor) @@ -117,11 +125,13 @@ def read_image(self, file_id): if file_id not in self.loaded_images: if file_id not in self.image_filenames: return None + filename = self.image_filenames[file_id] (_, extension) = os.path.splitext(filename) contents = self.zip_file.open(filename).read() _format = kurt.Image.image_format(extension) self.loaded_images[file_id] = kurt.Image(contents, _format) + return self.loaded_images[file_id] def read_waveform(self, file_id, rate, sample_count): @@ -129,7 +139,7 @@ def read_waveform(self, file_id, rate, sample_count): filename = self.sound_filenames[file_id] contents = self.zip_file.open(filename).read() self.loaded_sounds[file_id] = kurt.Waveform(contents, rate, - sample_count) + sample_count) return self.loaded_sounds[file_id] def finish(self): @@ -139,8 +149,7 @@ def load_scriptable(self, sd, is_stage=False): if is_stage: scriptable = kurt.Stage(self.project) elif 'objName' in sd: - scriptable = kurt.Sprite(self.project, - sd["objName"]) + scriptable = kurt.Sprite(self.project, sd["objName"]) else: return self.load_watcher(sd) @@ -162,19 +171,20 @@ def load_scriptable(self, sd, is_stage=False): if 'text' in cd: text_layer = self.read_image(cd['textLayerID']) + if text_layer: image = image.paste(text_layer) scriptable.costumes.append(kurt.Costume(cd['costumeName'], image, - rotation_center)) + rotation_center)) # sounds for snd in sd.get("sounds", []): - scriptable.sounds.append(kurt.Sound( - snd['soundName'], - self.read_waveform(snd['soundID'], snd['rate'], - snd['sampleCount']) - )) + scriptable.sounds.append(kurt.Sound(snd['soundName'], + self.read_waveform(snd['soundID'], snd['rate'], + snd['sampleCount']) + ) + ) # vars & lists target = self.project if is_stage else scriptable @@ -186,15 +196,16 @@ def load_scriptable(self, sd, is_stage=False): for ld in sd.get("lists", []): name = ld['listName'] target.lists[name] = kurt.List(ld['contents'], - ld['isPersistent']) + ld['isPersistent']) self.list_watchers.append(kurt.Watcher(target, - kurt.Block("contentsOfList:", name), is_visible=ld['visible'], - pos=(ld['x'], ld['y']))) + kurt.Block("contentsOfList:", name), is_visible=ld['visible'], + pos=(ld['x'], ld['y']))) # custom blocks first for script_array in sd.get("scripts", []): if script_array[2]: block_array = script_array[2][0] + if block_array[0] == 'procDef': (_, spec, input_names, defaults, is_atomic) = block_array cb = custom_block(spec, input_names, defaults) @@ -213,6 +224,7 @@ def load_scriptable(self, sd, is_stage=False): for comment_array in sd.get("scriptComments", []): (x, y, w, h, expanded, block_id, text) = comment_array + if block_id > -1: blocks_by_id[block_id].comment = text else: @@ -231,31 +243,34 @@ def load_scriptable(self, sd, is_stage=False): def load_watcher(self, wd): command = 'readVariable' if wd['cmd'] == 'getVar:' else wd['cmd'] - if wd['target'] == self.json['objName']: # Usually "Stage" + if wd['target'] == self.json['objName']: # Usually "Stage" target = self.project else: target = self.project.get_sprite(wd['target']) assert target + watcher = kurt.Watcher(target, - kurt.Block(command, *(wd['param'].split(',') if wd['param'] - else [])), - style=WATCHER_MODES[wd['mode']], - is_visible=wd['visible'], - pos=(wd['x'], wd['y']), - ) + kurt.Block(command, *(wd['param'].split(',') if wd['param'] + else [])), + style=WATCHER_MODES[wd['mode']], + is_visible=wd['visible'], + pos=(wd['x'], wd['y']), + ) watcher.slider_min = wd['sliderMin'] watcher.slider_max = wd['sliderMax'] + return watcher def load_block(self, block_array): block_array = list(block_array) command = block_array.pop(0) - if command == 'procDef': # CustomBlockType definition + if command == 'procDef': # CustomBlockType definition spec = block_array[0] + return kurt.Block('procDef', self.custom_blocks[spec]) - if command == 'call': # CustomBlockType call + if command == 'call': # CustomBlockType call block_type = self.custom_blocks[block_array.pop(0)] else: block_type = kurt.BlockType.get(command) @@ -264,10 +279,11 @@ def load_block(self, block_array): args = [] for arg in block_array: insert = inserts.pop(0) if inserts else None + if isinstance(arg, list): - if isinstance(arg[0], list): # 'stack'-shaped Insert + if isinstance(arg[0], list): # 'stack'-shaped Insert arg = map(self.load_block, arg) - else: # Block + else: # Block arg = self.load_block(arg) elif insert: if insert.shape == 'color': @@ -288,6 +304,7 @@ def load_block(self, block_array): def load_script(self, script_array): (x, y, blocks) = script_array blocks = map(self.load_block, blocks) + return kurt.Script(blocks, pos=(x, y)) def load_color(self, value): @@ -295,14 +312,15 @@ def load_color(self, value): value = struct.unpack('=I', struct.pack('=i', value))[0] # throw away leading ff, if any value &= 0x00ffffff - return kurt.Color( - (value & 0xff0000) >> 16, - (value & 0x00ff00) >> 8, - (value & 0x0000ff), - ) + + return kurt.Color((value & 0xff0000) >> 16, + (value & 0x00ff00) >> 8, + (value & 0x0000ff), + ) class ZipWriter(object): + def __init__(self, fp, project): self.zip_file = zipfile.ZipFile(fp, "w") self.image_dicts = {} @@ -316,9 +334,9 @@ def __init__(self, fp, project): "comment": project.notes, "author": project.author, "scriptCount": sum(len(s.scripts) - for s in [project.stage] + project.sprites), + for s in [project.stage] + project.sprites), "spriteCount": len(project.sprites), - "hasCloudData": False, # TODO + "hasCloudData": False, # TODO "videoOn": False, "userAgent": "", "flashVersion": "", @@ -331,6 +349,7 @@ def __init__(self, fp, project): sprites = {} for (i, sprite) in enumerate(project.sprites): sprites[sprite.name] = self.save_scriptable(sprite, i) + for actor in project.actors: if isinstance(actor, kurt.Sprite): actor = sprites[actor.name] @@ -347,6 +366,7 @@ def finish(self): def write_file(self, name, contents): """Write file contents string into archive.""" + # TODO: find a way to make ZipFile accept a file object. zi = zipfile.ZipInfo(name) zi.date_time = time.localtime(time.time())[:6] @@ -363,10 +383,11 @@ def write_image(self, image): self.write_file(filename, image.contents) self.image_dicts[image] = { - "baseLayerID": image_id, # -1 for download + "baseLayerID": image_id, # -1 for download "bitmapResolution": 1, "baseLayerMD5": hashlib.md5(image.contents).hexdigest() + ext, } + return self.image_dicts[image] def write_waveform(self, waveform): @@ -376,13 +397,14 @@ def write_waveform(self, waveform): self.write_file(filename, waveform.contents) self.waveform_dicts[waveform] = { - "soundID": waveform_id, # -1 for download + "soundID": waveform_id, # -1 for download "md5": hashlib.md5(waveform.contents).hexdigest() + \ - waveform.extension, + waveform.extension, "rate": waveform.rate, "sampleCount": waveform.sample_count, "format": "", } + return self.waveform_dicts[waveform] def save_watcher(self, watcher): @@ -403,10 +425,10 @@ def save_watcher(self, watcher): return { 'cmd': 'getVar:' if pbt.command == 'readVariable' else pbt.command, 'param': ",".join(map(unicode, watcher.block.args)) - if watcher.block.args else None, + if watcher.block.args else None, 'label': label, 'target': ('Stage' if isinstance(watcher.target, kurt.Project) - else watcher.target.name), + else watcher.target.name), 'mode': WATCHER_MODES.index(watcher.style), 'sliderMax': watcher.slider_max, 'sliderMin': watcher.slider_min, @@ -424,7 +446,7 @@ def save_scriptable(self, scriptable, i=None): "objName": scriptable.name, "currentCostumeIndex": scriptable.costume_index or 0, "scripts": filter(None, [self.save_script(s) for s in - scriptable.scripts]), + scriptable.scripts]), "scriptComments": [], "costumes": [self.save_costume(c) for c in scriptable.costumes], "sounds": [self.save_sound(c) for c in scriptable.sounds], @@ -463,7 +485,7 @@ def grab_comments(block): # sprite only if is_sprite: sd.update({ - "indexInLibrary": i+1, + "indexInLibrary": i + 1, "scratchX": scriptable.position[0], "scratchY": scriptable.position[1], "direction": scriptable.direction, @@ -511,6 +533,7 @@ def save_block(self, block): cb = block.args[0] spec = make_spec(cb.parts) input_names = [i.name for i in cb.inserts] + return ['procDef', spec, input_names, cb.defaults, cb.is_atomic] args = [] @@ -541,12 +564,14 @@ def save_block(self, block): def save_script(self, script): if isinstance(script, kurt.Script): (x, y) = script.pos or (10, 10) + return [x, y, map(self.save_block, script.blocks)] def save_comment(self, comment): (x, y) = comment.pos expanded = True h = 200 if expanded else 19 + return [x, y, 150, h, expanded, -1, comment.text] def save_costume(self, costume): @@ -557,6 +582,7 @@ def save_costume(self, costume): "rotationCenterX": rx, "rotationCenterY": ry, }) + return cd def save_sound(self, sound): @@ -564,13 +590,16 @@ def save_sound(self, sound): snd.update({ "soundName": sound.name, }) + return snd def save_color(self, color): # build RGB values value = (color.r << 16) + (color.g << 8) + color.b + # convert unsigned to signed 32-bit int value = struct.unpack('=i', struct.pack('=I', value))[0] + return value @@ -588,13 +617,14 @@ def load(self, fp): zl = ZipReader(fp) zl.project._original = zl.json zl.finish() + return zl.project def save(self, fp, project): zw = ZipWriter(fp, project) zw.finish() - return zw.json - + return zw.json +# register the current plugin Kurt.register(Scratch20Plugin()) diff --git a/kurt/scratch20/blocks.py b/kurt/scratch20/blocks.py index d133262..856bd19 100644 --- a/kurt/scratch20/blocks.py +++ b/kurt/scratch20/blocks.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2012 Tim Radvan # # This file is part of Kurt. @@ -18,7 +21,8 @@ import re import kurt -from kurt.scratch20.commands_src import commands, extras +from kurt.scratch20.commands_src import commands +from kurt.scratch20.commands_src_extras import commands_extra, extras CATEGORY_IDS = { @@ -37,8 +41,8 @@ 21: "wedo", 30: "midi", 91: "midi", - 98: "obsolete", # --> we should use the 1.4 blockspecs for these instead - 99: "obsolete", # scrolling + 98: "obsolete", # --> we should use the 1.4 blockspecs for these instead + 99: "obsolete", # scrolling # for stage? 102: "looks", @@ -50,10 +54,10 @@ SHAPE_FLAGS = { ' ': 'stack', 'b': 'boolean', - 'c': 'stack', # cblock + 'c': 'stack', # cblock 'r': 'reporter', - 'e': 'stack', # eblock - 'cf': 'cap', # cblock + 'e': 'stack', # eblock + 'cf': 'cap', # cblock 'f': 'cap', 'h': 'hat', } @@ -80,20 +84,26 @@ def parse_spec(spec, defaults): if INSERT_RE.match(part): default = defaults.pop(0) if defaults else None part = kurt.Insert(INSERT_SHAPES[part[:2]], part[3:] or None, - default=default) + default=default) + yield part + def make_spec(parts): spec = "" for part in parts: if isinstance(part, kurt.Insert): insert = part part = SHAPE_INSERTS[insert.shape] + if insert.kind: part += "." + insert.kind + spec += part + return spec + def blockify(blockspec): if len(blockspec) > 1: (spec, flag, category_id, command) = blockspec[:4] @@ -113,14 +123,19 @@ def blockify(blockspec): if pbt.text in ("wait until %s", "repeat until %s%s", "forever if %s%s"): pbt.inserts[0].unevaluated = True + return pbt else: return None + def make_block_types(): global commands - # Add extras + # add extra general commands + commands += commands_extra + + # add extras for block in extras: if len(block) > 1: (flag, spec, command) = block[:3] @@ -128,7 +143,7 @@ def make_block_types(): else: commands.append(block) - # Add not-actually-blocks + # add not-actually-blocks commands += [ ['%x.var', 'r', 9, 'readVariable', 'var'], ['%x.list', 'r', 12, "contentsOfList:", 'list'], @@ -137,14 +152,16 @@ def make_block_types(): ['%Z', ' ', 10, 'call'], ] - # Blockify + # blockify return map(blockify, commands) + def custom_block(spec, input_names, defaults): input_names = list(input_names) parts = list(parse_spec(spec, defaults)) + for part in parts: if isinstance(part, kurt.Insert): part.name = input_names.pop(0) - return kurt.CustomBlockType("stack", parts) + return kurt.CustomBlockType("stack", parts) diff --git a/kurt/scratch20/commands_src.py b/kurt/scratch20/commands_src.py index 0f387b5..cc293c9 100644 --- a/kurt/scratch20/commands_src.py +++ b/kurt/scratch20/commands_src.py @@ -1,284 +1,262 @@ -# Generated from Scratch SWF source by src/extract_blocks.py +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Generated by src/extract_blocks_20.py # commands:Array commands = [ - ['move %n steps', ' ', 1, 'forward:', 10], - ['turn @turnRight %n degrees', ' ', 1, 'turnRight:', 15], - ['turn @turnLeft %n degrees', ' ', 1, 'turnLeft:', 15], - ['--'], - ['point in direction %d.direction', ' ', 1, 'heading:', 90], - ['point towards %m.spriteOrMouse', ' ', 1, 'pointTowards:', ''], - ['--'], - ['go to x:%n y:%n', ' ', 1, 'gotoX:y:'], - ['go to %m.spriteOrMouse', ' ', 1, 'gotoSpriteOrMouse:', 'mouse-pointer'], - ['glide %n secs to x:%n y:%n', ' ', 1, 'glideSecs:toX:y:elapsed:from:'], - ['--'], - ['change x by %n', ' ', 1, 'changeXposBy:', 10], - ['set x to %n', ' ', 1, 'xpos:', 0], - ['change y by %n', ' ', 1, 'changeYposBy:', 10], - ['set y to %n', ' ', 1, 'ypos:', 0], - ['--'], - ['if on edge, bounce', ' ', 1, 'bounceOffEdge'], - ['-'], - ['set rotation style %m.rotationStyle', ' ', 1, 'setRotationStyle', 'left-right'], - ['--'], - ['x position', 'r', 1, 'xpos'], - ['y position', 'r', 1, 'ypos'], - ['direction', 'r', 1, 'heading'], - ['say %s for %n secs', ' ', 2, 'say:duration:elapsed:from:', 'Hello!', 2], - ['say %s', ' ', 2, 'say:', 'Hello!'], - ['think %s for %n secs', ' ', 2, 'think:duration:elapsed:from:', 'Hmm...', 2], - ['think %s', ' ', 2, 'think:', 'Hmm...'], - ['-'], - ['show', ' ', 2, 'show'], - ['hide', ' ', 2, 'hide'], - ['-'], - ['switch costume to %m.costume', ' ', 2, 'lookLike:', 'costume1'], - ['next costume', ' ', 2, 'nextCostume'], - ['switch backdrop to %m.backdrop', ' ', 2, 'startScene', 'backdrop1'], - ['-'], - ['change %m.effect effect by %n', ' ', 2, 'changeGraphicEffect:by:', 'color', 25], - ['set %m.effect effect to %n', ' ', 2, 'setGraphicEffect:to:', 'color', 0], - ['clear graphic effects', ' ', 2, 'filterReset'], - ['-'], - ['change size by %n', ' ', 2, 'changeSizeBy:', 10], - ['set size to %n%', ' ', 2, 'setSizeTo:', 100], - ['-'], - ['go to front', ' ', 2, 'comeToFront'], - ['go back %n layers', ' ', 2, 'goBackByLayers:', 1], - ['-'], - ['costume #', 'r', 2, 'costumeIndex'], - ['backdrop name', 'r', 2, 'sceneName'], - ['size', 'r', 2, 'scale'], - ['switch backdrop to %m.backdrop', ' ', 102, 'startScene', 'backdrop1'], - ['switch backdrop to %m.backdrop and wait', ' ', 102, 'startSceneAndWait', 'backdrop1'], - ['next backdrop', ' ', 102, 'nextScene'], - ['-'], - ['change %m.effect effect by %n', ' ', 102, 'changeGraphicEffect:by:', 'color', 25], - ['set %m.effect effect to %n', ' ', 102, 'setGraphicEffect:to:', 'color', 0], - ['clear graphic effects', ' ', 102, 'filterReset'], - ['-'], - ['backdrop name', 'r', 102, 'sceneName'], - ['backdrop #', 'r', 102, 'backgroundIndex'], - ['play sound %m.sound', ' ', 3, 'playSound:', 'pop'], - ['play sound %m.sound until done', ' ', 3, 'doPlaySoundAndWait', 'pop'], - ['stop all sounds', ' ', 3, 'stopAllSounds'], - ['-'], - ['play drum %d.drum for %n beats', ' ', 3, 'playDrum', 1, 0.2], - ['rest for %n beats', ' ', 3, 'rest:elapsed:from:', 0.2], - ['-'], - ['play note %d.note for %n beats', ' ', 3, 'noteOn:duration:elapsed:from:', 60, 0.5], - ['set instrument to %d.instrument', ' ', 3, 'instrument:', 1], - ['-'], - ['change volume by %n', ' ', 3, 'changeVolumeBy:', -10], - ['set volume to %n%', ' ', 3, 'setVolumeTo:', 100], - ['volume', 'r', 3, 'volume'], - ['-'], - ['change tempo by %n', ' ', 3, 'changeTempoBy:', 20], - ['set tempo to %n bpm', ' ', 3, 'setTempoTo:', 60], - ['tempo', 'r', 3, 'tempo'], - ['clear', ' ', 4, 'clearPenTrails'], - ['-'], - ['stamp', ' ', 4, 'stampCostume'], - ['-'], - ['pen down', ' ', 4, 'putPenDown'], - ['pen up', ' ', 4, 'putPenUp'], - ['-'], - ['set pen color to %c', ' ', 4, 'penColor:'], - ['change pen color by %n', ' ', 4, 'changePenHueBy:'], - ['set pen color to %n', ' ', 4, 'setPenHueTo:', 0], - ['-'], - ['change pen shade by %n', ' ', 4, 'changePenShadeBy:'], - ['set pen shade to %n', ' ', 4, 'setPenShadeTo:', 50], - ['-'], - ['change pen size by %n', ' ', 4, 'changePenSizeBy:', 1], - ['set pen size to %n', ' ', 4, 'penSize:', 1], - ['-'], - ['clear', ' ', 104, 'clearPenTrails'], - ['when @greenFlag clicked', 'h', 5, 'whenGreenFlag'], - ['when %m.key key pressed', 'h', 5, 'whenKeyPressed', 'space'], - ['when this sprite clicked', 'h', 5, 'whenClicked'], - ['when backdrop switches to %m.backdrop', 'h', 5, 'whenSceneStarts', 'backdrop1'], - ['--'], - ['when %m.triggerSensor > %n', 'h', 5, 'whenSensorGreaterThan', 'loudness', 10], - ['--'], - ['when I receive %m.broadcast', 'h', 5, 'whenIReceive', ''], - ['broadcast %m.broadcast', ' ', 5, 'broadcast:', ''], - ['broadcast %m.broadcast and wait', ' ', 5, 'doBroadcastAndWait', ''], - ['wait %n secs', ' ', 6, 'wait:elapsed:from:', 1], - ['-'], - ['repeat %n', 'c', 6, 'doRepeat', 10], - ['forever', 'cf', 6, 'doForever'], - ['-'], - ['if %b then', 'c', 6, 'doIf'], - ['if %b then', 'e', 6, 'doIfElse'], - ['wait until %b', ' ', 6, 'doWaitUntil'], - ['repeat until %b', 'c', 6, 'doUntil'], - ['-'], - ['stop %m.stop', 'f', 6, 'stopScripts', 'all'], - ['-'], - ['when I start as a clone', 'h', 6, 'whenCloned'], - ['create clone of %m.spriteOnly', ' ', 6, 'createCloneOf'], - ['delete this clone', 'f', 6, 'deleteClone'], - ['-'], - ['wait %n secs', ' ', 106, 'wait:elapsed:from:', 1], - ['-'], - ['repeat %n', 'c', 106, 'doRepeat', 10], - ['forever', 'cf', 106, 'doForever'], - ['-'], - ['if %b then', 'c', 106, 'doIf'], - ['if %b then', 'e', 106, 'doIfElse'], - ['wait until %b', ' ', 106, 'doWaitUntil'], - ['repeat until %b', 'c', 106, 'doUntil'], - ['-'], - ['stop %m.stop', 'f', 106, 'stopScripts', 'all'], - ['-'], - ['create clone of %m.spriteOnly', ' ', 106, 'createCloneOf'], - ['touching %m.touching?', 'b', 7, 'touching:', ''], - ['touching color %c?', 'b', 7, 'touchingColor:'], - ['color %c is touching %c?', 'b', 7, 'color:sees:'], - ['distance to %m.spriteOrMouse', 'r', 7, 'distanceTo:', ''], - ['-'], - ['ask %s and wait', ' ', 7, 'doAsk', "What's your name?"], - ['answer', 'r', 7, 'answer'], - ['-'], - ['key %m.key pressed?', 'b', 7, 'keyPressed:', 'space'], - ['mouse down?', 'b', 7, 'mousePressed'], - ['mouse x', 'r', 7, 'mouseX'], - ['mouse y', 'r', 7, 'mouseY'], - ['-'], - ['loudness', 'r', 7, 'soundLevel'], - ['-'], - ['video %m.videoMotionType on %m.stageOrThis', 'r', 7, 'senseVideoMotion', 'motion'], - ['turn video %m.videoState', ' ', 7, 'setVideoState', 'on'], - ['set video transparency to %n%', ' ', 7, 'setVideoTransparency', 50], - ['-'], - ['timer', 'r', 7, 'timer'], - ['reset timer', ' ', 7, 'timerReset'], - ['-'], - ['%m.attribute of %m.spriteOrStage', 'r', 7, 'getAttribute:of:'], - ['-'], - ['current %m.timeAndDate', 'r', 7, 'timeAndDate', 'minute'], - ['days since 2000', 'r', 7, 'timestamp'], - ['username', 'r', 7, 'getUserName'], - ['ask %s and wait', ' ', 107, 'doAsk', "What's your name?"], - ['answer', 'r', 107, 'answer'], - ['-'], - ['key %m.key pressed?', 'b', 107, 'keyPressed:', 'space'], - ['mouse down?', 'b', 107, 'mousePressed'], - ['mouse x', 'r', 107, 'mouseX'], - ['mouse y', 'r', 107, 'mouseY'], - ['-'], - ['loudness', 'r', 107, 'soundLevel'], - ['-'], - ['video %m.videoMotionType on %m.stageOrThis', 'r', 107, 'senseVideoMotion', 'motion', 'Stage'], - ['turn video %m.videoState', ' ', 107, 'setVideoState', 'on'], - ['set video transparency to %n%', ' ', 107, 'setVideoTransparency', 50], - ['-'], - ['timer', 'r', 107, 'timer'], - ['reset timer', ' ', 107, 'timerReset'], - ['-'], - ['%m.attribute of %m.spriteOrStage', 'r', 107, 'getAttribute:of:'], - ['-'], - ['current %m.timeAndDate', 'r', 107, 'timeAndDate', 'minute'], - ['days since 2000', 'r', 107, 'timestamp'], - ['username', 'r', 107, 'getUserName'], - ['%n + %n', 'r', 8, '+', '', ''], - ['%n - %n', 'r', 8, '-', '', ''], - ['%n * %n', 'r', 8, '*', '', ''], - ['%n / %n', 'r', 8, '/', '', ''], - ['-'], - ['pick random %n to %n', 'r', 8, 'randomFrom:to:', 1, 10], - ['-'], - ['%s < %s', 'b', 8, '<', '', ''], - ['%s = %s', 'b', 8, '=', '', ''], - ['%s > %s', 'b', 8, '>', '', ''], - ['-'], - ['%b and %b', 'b', 8, '&'], - ['%b or %b', 'b', 8, '|'], - ['not %b', 'b', 8, 'not'], - ['-'], - ['join %s %s', 'r', 8, 'concatenate:with:', 'hello ', 'world'], - ['letter %n of %s', 'r', 8, 'letter:of:', 1, 'world'], - ['length of %s', 'r', 8, 'stringLength:', 'world'], - ['-'], - ['%n mod %n', 'r', 8, '%', '', ''], - ['round %n', 'r', 8, 'rounded', ''], - ['-'], - ['%m.mathOp of %n', 'r', 8, 'computeFunction:of:', 'sqrt', 9], - ['set %m.var to %s', ' ', 9, 'setVar:to:'], - ['change %m.var by %n', ' ', 9, 'changeVar:by:'], - ['show variable %m.var', ' ', 9, 'showVariable:'], - ['hide variable %m.var', ' ', 9, 'hideVariable:'], - ['add %s to %m.list', ' ', 12, 'append:toList:'], - ['-'], - ['delete %d.listDeleteItem of %m.list', ' ', 12, 'deleteLine:ofList:'], - ['insert %s at %d.listItem of %m.list', ' ', 12, 'insert:at:ofList:'], - ['replace item %d.listItem of %m.list with %s', ' ', 12, 'setLine:ofList:to:'], - ['-'], - ['item %d.listItem of %m.list', 'r', 12, 'getLine:ofList:'], - ['length of %m.list', 'r', 12, 'lineCountOfList:'], - ['%m.list contains %s', 'b', 12, 'list:contains:'], - ['-'], - ['show list %m.list', ' ', 12, 'showList:'], - ['hide list %m.list', ' ', 12, 'hideList:'], - ['play drum %n for %n beats', ' ', 98, 'drum:duration:elapsed:from:', 1, 0.2], - ['set instrument to %n', ' ', 98, 'midiInstrument:', 1], - ['loud?', 'b', 98, 'isLoud'], - ['abs %n', 'r', 98, 'abs'], - ['sqrt %n', 'r', 98, 'sqrt'], - ['stop script', 'f', 98, 'doReturn'], - ['stop all', 'f', 98, 'stopAll'], - ['switch to background %m.costume', ' ', 98, 'showBackground:', 'backdrop1'], - ['next background', ' ', 98, 'nextBackground'], - ['forever if %b', 'cf', 98, 'doForeverIf'], - ['noop', 'r', 99, 'COUNT'], - ['counter', 'r', 99, 'COUNT'], - ['clear counter', ' ', 99, 'CLR_COUNT'], - ['incr counter', ' ', 99, 'INCR_COUNT'], - ['for each %m.varName in %s', 'c', 99, 'doForLoop', 'v', 10], - ['while %b', 'c', 99, 'doWhile'], - ['all at once', 'c', 99, 'warpSpeed'], - ['scroll right %n', ' ', 99, 'scrollRight', 10], - ['scroll up %n', ' ', 99, 'scrollUp', 10], - ['align scene %m.scrollAlign', ' ', 99, 'scrollAlign', 'bottom-left'], - ['x scroll', 'r', 99, 'xScroll'], - ['y scroll', 'r', 99, 'yScroll'], - ['hide all sprites', ' ', 99, 'hideAll'], - ['user id', 'r', 99, 'getUserId'], -] - -# SensorBoard():ScratchExtension -extras = [ - ['h', 'when %m.booleanSensor', 'whenSensorConnected', 'button pressed'], - ['-'], - ['b', 'sensor %m.booleanSensor?', 'sensorPressed:', 'button pressed'], - ['r', '%m.sensor sensor value', 'sensor:', 'slider'], -] - -# WeDo():ScratchExtension -extras += [ - [' ', 'turn motor on for %n secs', 'motorOnFor:elapsed:from:', 1], - [' ', 'turn motor on', 'allMotorsOn'], - [' ', 'turn motor off', 'allMotorsOff'], - [' ', 'set motor power %n', 'startMotorPower:', 100], - [' ', 'set motor direction %m.motorDirection', 'setMotorDirection:', 'this way'], - ['--'], - ['h', 'when distance < %n', 'whenDistanceLessThan', 20], - ['h', 'when tilt = %n', 'whenTiltIs', 1], - ['-'], - ['r', 'distance', 'wedoDistance'], - ['r', 'tilt', 'wedoTilt'], -] - -# MIDI():ScratchExtension -extras += [ - [' ', 'note on %d.note vel %n chan %n', 'noteOn', 60, 80, 0], - [' ', 'note off %d.note chan %n', 'noteOff', 60, 0], - [' ', 'pitch bend %n chan %n', 'pitchBend', 8192, 0], - [' ', 'set controller %n to %n chan %n', 'controller', 10, 127, 0], - [' ', 'set instrument to %n chan %n', 'program', 0, 0], - [' ', 'turn all notes off', 'midiReset'], - [' ', 'use java synthesizer %b', 'useJavaSynth'], - ['r', 'midi time', 'v.time'], + ['move %n steps', ' ', 1, 'forward:', 10], + ['turn @turnRight %n degrees', ' ', 1, 'turnRight:', 15], + ['turn @turnLeft %n degrees', ' ', 1, 'turnLeft:', 15], + ['--'], + ['point in direction %d.direction', ' ', 1, 'heading:', 90], + ['point towards %m.spriteOrMouse', ' ', 1, 'pointTowards:', ''], + ['--'], + ['go to x:%n y:%n', ' ', 1, 'gotoX:y:'], + ['go to %m.spriteOrMouse', ' ', 1, 'gotoSpriteOrMouse:', 'mouse-pointer'], + ['glide %n secs to x:%n y:%n', ' ', 1, 'glideSecs:toX:y:elapsed:from:'], + ['--'], + ['change x by %n', ' ', 1, 'changeXposBy:', 10], + ['set x to %n', ' ', 1, 'xpos:', 0], + ['change y by %n', ' ', 1, 'changeYposBy:', 10], + ['set y to %n', ' ', 1, 'ypos:', 0], + ['--'], + ['if on edge, bounce', ' ', 1, 'bounceOffEdge'], + ['-'], + ['set rotation style %m.rotationStyle', ' ', + 1, 'setRotationStyle', 'left-right'], + ['--'], + ['x position', 'r', 1, 'xpos'], + ['y position', 'r', 1, 'ypos'], + ['direction', 'r', 1, 'heading'], + ['say %s for %n secs', ' ', 2, 'say:duration:elapsed:from:', 'Hello!', 2], + ['say %s', ' ', 2, 'say:', 'Hello!'], + ['think %s for %n secs', ' ', 2, + 'think:duration:elapsed:from:', 'Hmm...', 2], + ['think %s', ' ', 2, 'think:', 'Hmm...'], + ['-'], + ['show', ' ', 2, 'show'], + ['hide', ' ', 2, 'hide'], + ['-'], + ['switch costume to %m.costume', ' ', 2, 'lookLike:', 'costume1'], + ['next costume', ' ', 2, 'nextCostume'], + ['switch backdrop to %m.backdrop', ' ', 2, 'startScene', 'backdrop1'], + ['-'], + ['change %m.effect effect by %n', ' ', 2, + 'changeGraphicEffect:by:', 'color', 25], + ['set %m.effect effect to %n', ' ', 2, 'setGraphicEffect:to:', 'color', 0], + ['clear graphic effects', ' ', 2, 'filterReset'], + ['-'], + ['change size by %n', ' ', 2, 'changeSizeBy:', 10], + ['set size to %n%', ' ', 2, 'setSizeTo:', 100], + ['-'], + ['go to front', ' ', 2, 'comeToFront'], + ['go back %n layers', ' ', 2, 'goBackByLayers:', 1], + ['-'], + ['costume #', 'r', 2, 'costumeIndex'], + ['backdrop name', 'r', 2, 'sceneName'], + ['size', 'r', 2, 'scale'], + ['switch backdrop to %m.backdrop', ' ', 102, 'startScene', 'backdrop1'], + ['switch backdrop to %m.backdrop and wait', + ' ', 102, 'startSceneAndWait', 'backdrop1'], + ['next backdrop', ' ', 102, 'nextScene'], + ['-'], + ['change %m.effect effect by %n', ' ', 102, + 'changeGraphicEffect:by:', 'color', 25], + ['set %m.effect effect to %n', ' ', 102, + 'setGraphicEffect:to:', 'color', 0], + ['clear graphic effects', ' ', 102, 'filterReset'], + ['-'], + ['backdrop name', 'r', 102, 'sceneName'], + ['backdrop #', 'r', 102, 'backgroundIndex'], + ['play sound %m.sound', ' ', 3, 'playSound:', 'pop'], + ['play sound %m.sound until done', ' ', 3, 'doPlaySoundAndWait', 'pop'], + ['stop all sounds', ' ', 3, 'stopAllSounds'], + ['-'], + ['play drum %d.drum for %n beats', ' ', 3, 'playDrum', 1, 0.25], + ['rest for %n beats', ' ', 3, 'rest:elapsed:from:', 0.25], + ['-'], + ['play note %d.note for %n beats', ' ', 3, + 'noteOn:duration:elapsed:from:', 60, 0.5], + ['set instrument to %d.instrument', ' ', 3, 'instrument:', 1], + ['-'], + ['change volume by %n', ' ', 3, 'changeVolumeBy:', -10], + ['set volume to %n%', ' ', 3, 'setVolumeTo:', 100], + ['volume', 'r', 3, 'volume'], + ['-'], + ['change tempo by %n', ' ', 3, 'changeTempoBy:', 20], + ['set tempo to %n bpm', ' ', 3, 'setTempoTo:', 60], + ['tempo', 'r', 3, 'tempo'], + ['clear', ' ', 4, 'clearPenTrails'], + ['-'], + ['stamp', ' ', 4, 'stampCostume'], + ['-'], + ['pen down', ' ', 4, 'putPenDown'], + ['pen up', ' ', 4, 'putPenUp'], + ['-'], + ['set pen color to %c', ' ', 4, 'penColor:'], + ['change pen color by %n', ' ', 4, 'changePenHueBy:'], + ['set pen color to %n', ' ', 4, 'setPenHueTo:', 0], + ['-'], + ['change pen shade by %n', ' ', 4, 'changePenShadeBy:'], + ['set pen shade to %n', ' ', 4, 'setPenShadeTo:', 50], + ['-'], + ['change pen size by %n', ' ', 4, 'changePenSizeBy:', 1], + ['set pen size to %n', ' ', 4, 'penSize:', 1], + ['-'], + ['clear', ' ', 104, 'clearPenTrails'], + ['when @greenFlag clicked', 'h', 5, 'whenGreenFlag'], + ['when %m.key key pressed', 'h', 5, 'whenKeyPressed', 'space'], + ['when this sprite clicked', 'h', 5, 'whenClicked'], + ['when backdrop switches to %m.backdrop', + 'h', 5, 'whenSceneStarts', 'backdrop1'], + ['--'], + ['when %m.triggerSensor > %n', 'h', 5, + 'whenSensorGreaterThan', 'loudness', 10], + ['--'], + ['when I receive %m.broadcast', 'h', 5, 'whenIReceive', ''], + ['broadcast %m.broadcast', ' ', 5, 'broadcast:', ''], + ['broadcast %m.broadcast and wait', ' ', 5, 'doBroadcastAndWait', ''], + ['wait %n secs', ' ', 6, 'wait:elapsed:from:', 1], + ['-'], + ['repeat %n', 'c', 6, 'doRepeat', 10], + ['forever', 'cf', 6, 'doForever'], + ['-'], + ['if %b then', 'c', 6, 'doIf'], + ['if %b then', 'e', 6, 'doIfElse'], + ['wait until %b', ' ', 6, 'doWaitUntil'], + ['repeat until %b', 'c', 6, 'doUntil'], + ['-'], + ['stop %m.stop', 'f', 6, 'stopScripts', 'all'], + ['-'], + ['when I start as a clone', 'h', 6, 'whenCloned'], + ['create clone of %m.spriteOnly', ' ', 6, 'createCloneOf'], + ['delete this clone', 'f', 6, 'deleteClone'], + ['-'], + ['wait %n secs', ' ', 106, 'wait:elapsed:from:', 1], + ['-'], + ['repeat %n', 'c', 106, 'doRepeat', 10], + ['forever', 'cf', 106, 'doForever'], + ['-'], + ['if %b then', 'c', 106, 'doIf'], + ['if %b then', 'e', 106, 'doIfElse'], + ['wait until %b', ' ', 106, 'doWaitUntil'], + ['repeat until %b', 'c', 106, 'doUntil'], + ['-'], + ['stop %m.stop', 'f', 106, 'stopScripts', 'all'], + ['-'], + ['create clone of %m.spriteOnly', ' ', 106, 'createCloneOf'], + ['touching %m.touching?', 'b', 7, 'touching:', ''], + ['touching color %c?', 'b', 7, 'touchingColor:'], + ['color %c is touching %c?', 'b', 7, 'color:sees:'], + ['distance to %m.spriteOrMouse', 'r', 7, 'distanceTo:', ''], + ['-'], + ['ask %s and wait', ' ', 7, 'doAsk', "What's your name?"], + ['answer', 'r', 7, 'answer'], + ['-'], + ['key %m.key pressed?', 'b', 7, 'keyPressed:', 'space'], + ['mouse down?', 'b', 7, 'mousePressed'], + ['mouse x', 'r', 7, 'mouseX'], + ['mouse y', 'r', 7, 'mouseY'], + ['-'], + ['loudness', 'r', 7, 'soundLevel'], + ['-'], + ['video %m.videoMotionType on %m.stageOrThis', + 'r', 7, 'senseVideoMotion', 'motion'], + ['turn video %m.videoState', ' ', 7, 'setVideoState', 'on'], + ['set video transparency to %n%', ' ', 7, 'setVideoTransparency', 50], + ['-'], + ['timer', 'r', 7, 'timer'], + ['reset timer', ' ', 7, 'timerReset'], + ['-'], + ['%m.attribute of %m.spriteOrStage', 'r', 7, 'getAttribute:of:'], + ['-'], + ['current %m.timeAndDate', 'r', 7, 'timeAndDate', 'minute'], + ['days since 2000', 'r', 7, 'timestamp'], + ['username', 'r', 7, 'getUserName'], + ['ask %s and wait', ' ', 107, 'doAsk', "What's your name?"], + ['answer', 'r', 107, 'answer'], + ['-'], + ['key %m.key pressed?', 'b', 107, 'keyPressed:', 'space'], + ['mouse down?', 'b', 107, 'mousePressed'], + ['mouse x', 'r', 107, 'mouseX'], + ['mouse y', 'r', 107, 'mouseY'], + ['-'], + ['loudness', 'r', 107, 'soundLevel'], + ['-'], + ['video %m.videoMotionType on %m.stageOrThis', 'r', + 107, 'senseVideoMotion', 'motion', 'Stage'], + ['turn video %m.videoState', ' ', 107, 'setVideoState', 'on'], + ['set video transparency to %n%', ' ', 107, 'setVideoTransparency', 50], + ['-'], + ['timer', 'r', 107, 'timer'], + ['reset timer', ' ', 107, 'timerReset'], + ['-'], + ['%m.attribute of %m.spriteOrStage', 'r', 107, 'getAttribute:of:'], + ['-'], + ['current %m.timeAndDate', 'r', 107, 'timeAndDate', 'minute'], + ['days since 2000', 'r', 107, 'timestamp'], + ['username', 'r', 107, 'getUserName'], + ['%n + %n', 'r', 8, '+', '', ''], + ['%n - %n', 'r', 8, '-', '', ''], + ['%n * %n', 'r', 8, '*', '', ''], + ['%n / %n', 'r', 8, '/', '', ''], + ['-'], + ['pick random %n to %n', 'r', 8, 'randomFrom:to:', 1, 10], + ['-'], + ['%s < %s', 'b', 8, '<', '', ''], + ['%s = %s', 'b', 8, '=', '', ''], + ['%s > %s', 'b', 8, '>', '', ''], + ['-'], + ['%b and %b', 'b', 8, '&'], + ['%b or %b', 'b', 8, '|'], + ['not %b', 'b', 8, 'not'], + ['-'], + ['join %s %s', 'r', 8, 'concatenate:with:', 'hello ', 'world'], + ['letter %n of %s', 'r', 8, 'letter:of:', 1, 'world'], + ['length of %s', 'r', 8, 'stringLength:', 'world'], + ['-'], + ['%n mod %n', 'r', 8, '%', '', ''], + ['round %n', 'r', 8, 'rounded', ''], + ['-'], + ['%m.mathOp of %n', 'r', 8, 'computeFunction:of:', 'sqrt', 9], + ['show variable %m.var', ' ', 9, 'showVariable:'], + ['hide variable %m.var', ' ', 9, 'hideVariable:'], + ['add %s to %m.list', ' ', 12, 'append:toList:'], + ['-'], + ['delete %d.listDeleteItem of %m.list', ' ', 12, 'deleteLine:ofList:'], + ['insert %s at %d.listItem of %m.list', ' ', 12, 'insert:at:ofList:'], + ['replace item %d.listItem of %m.list with %s', + ' ', 12, 'setLine:ofList:to:'], + ['-'], + ['item %d.listItem of %m.list', 'r', 12, 'getLine:ofList:'], + ['length of %m.list', 'r', 12, 'lineCountOfList:'], + ['%m.list contains %s', 'b', 12, 'list:contains:'], + ['-'], + ['show list %m.list', ' ', 12, 'showList:'], + ['hide list %m.list', ' ', 12, 'hideList:'], + ['set instrument to %n', ' ', 98, 'midiInstrument:', 1], + ['loud?', 'b', 98, 'isLoud'], + ['abs %n', 'r', 98, 'abs'], + ['sqrt %n', 'r', 98, 'sqrt'], + ['stop script', 'f', 98, 'doReturn'], + ['stop all', 'f', 98, 'stopAll'], + ['switch to background %m.costume', ' ', + 98, 'showBackground:', 'backdrop1'], + ['next background', ' ', 98, 'nextBackground'], + ['forever if %b', 'cf', 98, 'doForeverIf'], + ['noop', 'r', 99, 'COUNT'], + ['counter', 'r', 99, 'COUNT'], + ['clear counter', ' ', 99, 'CLR_COUNT'], + ['incr counter', ' ', 99, 'INCR_COUNT'], + ['for each %m.varName in %s', 'c', 99, 'doForLoop', 'v', 10], + ['while %b', 'c', 99, 'doWhile'], + ['all at once', 'c', 99, 'warpSpeed'], + ['scroll right %n', ' ', 99, 'scrollRight', 10], + ['scroll up %n', ' ', 99, 'scrollUp', 10], + ['align scene %m.scrollAlign', ' ', 99, 'scrollAlign', 'bottom-left'], + ['x scroll', 'r', 99, 'xScroll'], + ['y scroll', 'r', 99, 'yScroll'], + ['hide all sprites', ' ', 99, 'hideAll'], + ['user id', 'r', 99, 'getUserId'], ] diff --git a/kurt/scratch20/commands_src_bk.py b/kurt/scratch20/commands_src_bk.py new file mode 100644 index 0000000..8aba206 --- /dev/null +++ b/kurt/scratch20/commands_src_bk.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Generated from Scratch SWF source by src/extract_blocks.py + +# commands:Array +commands = [ + ['move %n steps', ' ', 1, 'forward:', 10], + ['turn @turnRight %n degrees', ' ', 1, 'turnRight:', 15], + ['turn @turnLeft %n degrees', ' ', 1, 'turnLeft:', 15], + ['--'], + ['point in direction %d.direction', ' ', 1, 'heading:', 90], + ['point towards %m.spriteOrMouse', ' ', 1, 'pointTowards:', ''], + ['--'], + ['go to x:%n y:%n', ' ', 1, 'gotoX:y:'], + ['go to %m.spriteOrMouse', ' ', 1, 'gotoSpriteOrMouse:', 'mouse-pointer'], + ['glide %n secs to x:%n y:%n', ' ', 1, 'glideSecs:toX:y:elapsed:from:'], + ['--'], + ['change x by %n', ' ', 1, 'changeXposBy:', 10], + ['set x to %n', ' ', 1, 'xpos:', 0], + ['change y by %n', ' ', 1, 'changeYposBy:', 10], + ['set y to %n', ' ', 1, 'ypos:', 0], + ['--'], + ['if on edge, bounce', ' ', 1, 'bounceOffEdge'], + ['-'], + ['set rotation style %m.rotationStyle', ' ', + 1, 'setRotationStyle', 'left-right'], + ['--'], + ['x position', 'r', 1, 'xpos'], + ['y position', 'r', 1, 'ypos'], + ['direction', 'r', 1, 'heading'], + ['say %s for %n secs', ' ', 2, 'say:duration:elapsed:from:', 'Hello!', 2], + ['say %s', ' ', 2, 'say:', 'Hello!'], + ['think %s for %n secs', ' ', 2, + 'think:duration:elapsed:from:', 'Hmm...', 2], + ['think %s', ' ', 2, 'think:', 'Hmm...'], + ['-'], + ['show', ' ', 2, 'show'], + ['hide', ' ', 2, 'hide'], + ['-'], + ['switch costume to %m.costume', ' ', 2, 'lookLike:', 'costume1'], + ['next costume', ' ', 2, 'nextCostume'], + ['switch backdrop to %m.backdrop', ' ', 2, 'startScene', 'backdrop1'], + ['-'], + ['change %m.effect effect by %n', ' ', 2, + 'changeGraphicEffect:by:', 'color', 25], + ['set %m.effect effect to %n', ' ', 2, 'setGraphicEffect:to:', 'color', 0], + ['clear graphic effects', ' ', 2, 'filterReset'], + ['-'], + ['change size by %n', ' ', 2, 'changeSizeBy:', 10], + ['set size to %n%', ' ', 2, 'setSizeTo:', 100], + ['-'], + ['go to front', ' ', 2, 'comeToFront'], + ['go back %n layers', ' ', 2, 'goBackByLayers:', 1], + ['-'], + ['costume #', 'r', 2, 'costumeIndex'], + ['backdrop name', 'r', 2, 'sceneName'], + ['size', 'r', 2, 'scale'], + ['switch backdrop to %m.backdrop', ' ', 102, 'startScene', 'backdrop1'], + ['switch backdrop to %m.backdrop and wait', + ' ', 102, 'startSceneAndWait', 'backdrop1'], + ['next backdrop', ' ', 102, 'nextScene'], + ['-'], + ['change %m.effect effect by %n', ' ', 102, + 'changeGraphicEffect:by:', 'color', 25], + ['set %m.effect effect to %n', ' ', 102, + 'setGraphicEffect:to:', 'color', 0], + ['clear graphic effects', ' ', 102, 'filterReset'], + ['-'], + ['backdrop name', 'r', 102, 'sceneName'], + ['backdrop #', 'r', 102, 'backgroundIndex'], + ['play sound %m.sound', ' ', 3, 'playSound:', 'pop'], + ['play sound %m.sound until done', ' ', 3, 'doPlaySoundAndWait', 'pop'], + ['stop all sounds', ' ', 3, 'stopAllSounds'], + ['-'], + ['play drum %d.drum for %n beats', ' ', 3, 'playDrum', 1, 0.2], + ['rest for %n beats', ' ', 3, 'rest:elapsed:from:', 0.2], + ['-'], + ['play note %d.note for %n beats', ' ', 3, + 'noteOn:duration:elapsed:from:', 60, 0.5], + ['set instrument to %d.instrument', ' ', 3, 'instrument:', 1], + ['-'], + ['change volume by %n', ' ', 3, 'changeVolumeBy:', -10], + ['set volume to %n%', ' ', 3, 'setVolumeTo:', 100], + ['volume', 'r', 3, 'volume'], + ['-'], + ['change tempo by %n', ' ', 3, 'changeTempoBy:', 20], + ['set tempo to %n bpm', ' ', 3, 'setTempoTo:', 60], + ['tempo', 'r', 3, 'tempo'], + ['clear', ' ', 4, 'clearPenTrails'], + ['-'], + ['stamp', ' ', 4, 'stampCostume'], + ['-'], + ['pen down', ' ', 4, 'putPenDown'], + ['pen up', ' ', 4, 'putPenUp'], + ['-'], + ['set pen color to %c', ' ', 4, 'penColor:'], + ['change pen color by %n', ' ', 4, 'changePenHueBy:'], + ['set pen color to %n', ' ', 4, 'setPenHueTo:', 0], + ['-'], + ['change pen shade by %n', ' ', 4, 'changePenShadeBy:'], + ['set pen shade to %n', ' ', 4, 'setPenShadeTo:', 50], + ['-'], + ['change pen size by %n', ' ', 4, 'changePenSizeBy:', 1], + ['set pen size to %n', ' ', 4, 'penSize:', 1], + ['-'], + ['clear', ' ', 104, 'clearPenTrails'], + ['when @greenFlag clicked', 'h', 5, 'whenGreenFlag'], + ['when %m.key key pressed', 'h', 5, 'whenKeyPressed', 'space'], + ['when this sprite clicked', 'h', 5, 'whenClicked'], + ['when backdrop switches to %m.backdrop', + 'h', 5, 'whenSceneStarts', 'backdrop1'], + ['--'], + ['when %m.triggerSensor > %n', 'h', 5, + 'whenSensorGreaterThan', 'loudness', 10], + ['--'], + ['when I receive %m.broadcast', 'h', 5, 'whenIReceive', ''], + ['broadcast %m.broadcast', ' ', 5, 'broadcast:', ''], + ['broadcast %m.broadcast and wait', ' ', 5, 'doBroadcastAndWait', ''], + ['wait %n secs', ' ', 6, 'wait:elapsed:from:', 1], + ['-'], + ['repeat %n', 'c', 6, 'doRepeat', 10], + ['forever', 'cf', 6, 'doForever'], + ['-'], + ['if %b then', 'c', 6, 'doIf'], + ['if %b then', 'e', 6, 'doIfElse'], + ['wait until %b', ' ', 6, 'doWaitUntil'], + ['repeat until %b', 'c', 6, 'doUntil'], + ['-'], + ['stop %m.stop', 'f', 6, 'stopScripts', 'all'], + ['-'], + ['when I start as a clone', 'h', 6, 'whenCloned'], + ['create clone of %m.spriteOnly', ' ', 6, 'createCloneOf'], + ['delete this clone', 'f', 6, 'deleteClone'], + ['-'], + ['wait %n secs', ' ', 106, 'wait:elapsed:from:', 1], + ['-'], + ['repeat %n', 'c', 106, 'doRepeat', 10], + ['forever', 'cf', 106, 'doForever'], + ['-'], + ['if %b then', 'c', 106, 'doIf'], + ['if %b then', 'e', 106, 'doIfElse'], + ['wait until %b', ' ', 106, 'doWaitUntil'], + ['repeat until %b', 'c', 106, 'doUntil'], + ['-'], + ['stop %m.stop', 'f', 106, 'stopScripts', 'all'], + ['-'], + ['create clone of %m.spriteOnly', ' ', 106, 'createCloneOf'], + ['touching %m.touching?', 'b', 7, 'touching:', ''], + ['touching color %c?', 'b', 7, 'touchingColor:'], + ['color %c is touching %c?', 'b', 7, 'color:sees:'], + ['distance to %m.spriteOrMouse', 'r', 7, 'distanceTo:', ''], + ['-'], + ['ask %s and wait', ' ', 7, 'doAsk', "What's your name?"], + ['answer', 'r', 7, 'answer'], + ['-'], + ['key %m.key pressed?', 'b', 7, 'keyPressed:', 'space'], + ['mouse down?', 'b', 7, 'mousePressed'], + ['mouse x', 'r', 7, 'mouseX'], + ['mouse y', 'r', 7, 'mouseY'], + ['-'], + ['loudness', 'r', 7, 'soundLevel'], + ['-'], + ['video %m.videoMotionType on %m.stageOrThis', + 'r', 7, 'senseVideoMotion', 'motion'], + ['turn video %m.videoState', ' ', 7, 'setVideoState', 'on'], + ['set video transparency to %n%', ' ', 7, 'setVideoTransparency', 50], + ['-'], + ['timer', 'r', 7, 'timer'], + ['reset timer', ' ', 7, 'timerReset'], + ['-'], + ['%m.attribute of %m.spriteOrStage', 'r', 7, 'getAttribute:of:'], + ['-'], + ['current %m.timeAndDate', 'r', 7, 'timeAndDate', 'minute'], + ['days since 2000', 'r', 7, 'timestamp'], + ['username', 'r', 7, 'getUserName'], + ['ask %s and wait', ' ', 107, 'doAsk', "What's your name?"], + ['answer', 'r', 107, 'answer'], + ['-'], + ['key %m.key pressed?', 'b', 107, 'keyPressed:', 'space'], + ['mouse down?', 'b', 107, 'mousePressed'], + ['mouse x', 'r', 107, 'mouseX'], + ['mouse y', 'r', 107, 'mouseY'], + ['-'], + ['loudness', 'r', 107, 'soundLevel'], + ['-'], + ['video %m.videoMotionType on %m.stageOrThis', 'r', + 107, 'senseVideoMotion', 'motion', 'Stage'], + ['turn video %m.videoState', ' ', 107, 'setVideoState', 'on'], + ['set video transparency to %n%', ' ', 107, 'setVideoTransparency', 50], + ['-'], + ['timer', 'r', 107, 'timer'], + ['reset timer', ' ', 107, 'timerReset'], + ['-'], + ['%m.attribute of %m.spriteOrStage', 'r', 107, 'getAttribute:of:'], + ['-'], + ['current %m.timeAndDate', 'r', 107, 'timeAndDate', 'minute'], + ['days since 2000', 'r', 107, 'timestamp'], + ['username', 'r', 107, 'getUserName'], + ['%n + %n', 'r', 8, '+', '', ''], + ['%n - %n', 'r', 8, '-', '', ''], + ['%n * %n', 'r', 8, '*', '', ''], + ['%n / %n', 'r', 8, '/', '', ''], + ['-'], + ['pick random %n to %n', 'r', 8, 'randomFrom:to:', 1, 10], + ['-'], + ['%s < %s', 'b', 8, '<', '', ''], + ['%s = %s', 'b', 8, '=', '', ''], + ['%s > %s', 'b', 8, '>', '', ''], + ['-'], + ['%b and %b', 'b', 8, '&'], + ['%b or %b', 'b', 8, '|'], + ['not %b', 'b', 8, 'not'], + ['-'], + ['join %s %s', 'r', 8, 'concatenate:with:', 'hello ', 'world'], + ['letter %n of %s', 'r', 8, 'letter:of:', 1, 'world'], + ['length of %s', 'r', 8, 'stringLength:', 'world'], + ['-'], + ['%n mod %n', 'r', 8, '%', '', ''], + ['round %n', 'r', 8, 'rounded', ''], + ['-'], + ['%m.mathOp of %n', 'r', 8, 'computeFunction:of:', 'sqrt', 9], + ['set %m.var to %s', ' ', 9, 'setVar:to:'], + ['change %m.var by %n', ' ', 9, 'changeVar:by:'], + ['show variable %m.var', ' ', 9, 'showVariable:'], + ['hide variable %m.var', ' ', 9, 'hideVariable:'], + ['add %s to %m.list', ' ', 12, 'append:toList:'], + ['-'], + ['delete %d.listDeleteItem of %m.list', ' ', 12, 'deleteLine:ofList:'], + ['insert %s at %d.listItem of %m.list', ' ', 12, 'insert:at:ofList:'], + ['replace item %d.listItem of %m.list with %s', + ' ', 12, 'setLine:ofList:to:'], + ['-'], + ['item %d.listItem of %m.list', 'r', 12, 'getLine:ofList:'], + ['length of %m.list', 'r', 12, 'lineCountOfList:'], + ['%m.list contains %s', 'b', 12, 'list:contains:'], + ['-'], + ['show list %m.list', ' ', 12, 'showList:'], + ['hide list %m.list', ' ', 12, 'hideList:'], + ['play drum %n for %n beats', ' ', 98, + 'drum:duration:elapsed:from:', 1, 0.2], + ['set instrument to %n', ' ', 98, 'midiInstrument:', 1], + ['loud?', 'b', 98, 'isLoud'], + ['abs %n', 'r', 98, 'abs'], + ['sqrt %n', 'r', 98, 'sqrt'], + ['stop script', 'f', 98, 'doReturn'], + ['stop all', 'f', 98, 'stopAll'], + ['switch to background %m.costume', ' ', + 98, 'showBackground:', 'backdrop1'], + ['next background', ' ', 98, 'nextBackground'], + ['forever if %b', 'cf', 98, 'doForeverIf'], + ['noop', 'r', 99, 'COUNT'], + ['counter', 'r', 99, 'COUNT'], + ['clear counter', ' ', 99, 'CLR_COUNT'], + ['incr counter', ' ', 99, 'INCR_COUNT'], + ['for each %m.varName in %s', 'c', 99, 'doForLoop', 'v', 10], + ['while %b', 'c', 99, 'doWhile'], + ['all at once', 'c', 99, 'warpSpeed'], + ['scroll right %n', ' ', 99, 'scrollRight', 10], + ['scroll up %n', ' ', 99, 'scrollUp', 10], + ['align scene %m.scrollAlign', ' ', 99, 'scrollAlign', 'bottom-left'], + ['x scroll', 'r', 99, 'xScroll'], + ['y scroll', 'r', 99, 'yScroll'], + ['hide all sprites', ' ', 99, 'hideAll'], + ['user id', 'r', 99, 'getUserId'], +] + +# SensorBoard():ScratchExtension +extras = [ + ['h', 'when %m.booleanSensor', 'whenSensorConnected', 'button pressed'], + ['-'], + ['b', 'sensor %m.booleanSensor?', 'sensorPressed:', 'button pressed'], + ['r', '%m.sensor sensor value', 'sensor:', 'slider'], +] + +# WeDo():ScratchExtension +extras += [ + [' ', 'turn motor on for %n secs', 'motorOnFor:elapsed:from:', 1], + [' ', 'turn motor on', 'allMotorsOn'], + [' ', 'turn motor off', 'allMotorsOff'], + [' ', 'set motor power %n', 'startMotorPower:', 100], + [' ', 'set motor direction %m.motorDirection', + 'setMotorDirection:', 'this way'], + ['--'], + ['h', 'when distance < %n', 'whenDistanceLessThan', 20], + ['h', 'when tilt = %n', 'whenTiltIs', 1], + ['-'], + ['r', 'distance', 'wedoDistance'], + ['r', 'tilt', 'wedoTilt'], +] + +# MIDI():ScratchExtension +extras += [ + [' ', 'note on %d.note vel %n chan %n', 'noteOn', 60, 80, 0], + [' ', 'note off %d.note chan %n', 'noteOff', 60, 0], + [' ', 'pitch bend %n chan %n', 'pitchBend', 8192, 0], + [' ', 'set controller %n to %n chan %n', 'controller', 10, 127, 0], + [' ', 'set instrument to %n chan %n', 'program', 0, 0], + [' ', 'turn all notes off', 'midiReset'], + [' ', 'use java synthesizer %b', 'useJavaSynth'], + ['r', 'midi time', 'v.time'], +] diff --git a/kurt/scratch20/commands_src_extras.py b/kurt/scratch20/commands_src_extras.py new file mode 100644 index 0000000..a70416a --- /dev/null +++ b/kurt/scratch20/commands_src_extras.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# commands:Array +commands_extra = [ + # manually added + ['set %m.var to %s', ' ', 9, 'setVar:to:'], + ['change %m.var by %n', ' ', 9, 'changeVar:by:'], +] + +# extension:Array +extras = [] + +# robotics:ScratchExtension +extras += [ + # add extensions code if not auto-generated + [' ', 'stop robot-drone', 'Scratch2JdeRobot/stop'], + [' ', 'move robot %m.robotDirections', 'Scratch2JdeRobot/robot/move', 'forward'], + [' ', 'move drone %m.droneDirections', 'Scratch2JdeRobot/drone/move', 'forward'], + [' ', 'move robot %m.direction speed %n', 'Scratch2JdeRobot/robot/move/speed', 'forward', 1], + [' ', 'turn robot-drone %m.turnDirections', 'Scratch2JdeRobot/turn', 'left'], + [' ', 'turn robot %m.turnDirections speed %n', 'Scratch2JdeRobot/turn/speed', 'left', 1], + [' ', 'take off drone', 'Scratch2JdeRobot/drone/takeoff'], + [' ', 'land drone', 'Scratch2JdeRobot/drone/land'], + ['r', 'frontal laser distance', 'Scratch2JdeRobot/laser/frontal'] +] diff --git a/kurt/text.py b/kurt/text.py index b97c9f9..2883169 100644 --- a/kurt/text.py +++ b/kurt/text.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2012 Tim Radvan # # This file is part of Kurt. @@ -26,79 +29,108 @@ """ -import re from collections import OrderedDict import kurt - - +import re #-- Tokens --# + class Token(object): + def __init__(self, value): self.value = value + def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.value) + class symbol(Token): lbp = 0 + class literal(Token): + def nud(self): return self.value + class number(Token): + def __init__(self, value): value = float(value) + if int(value) == value: value = int(value) + self.value = value + def nud(self): return self + class string(Token): lbp = 0 + def nud(self): return self + class color(Token): + def nud(self): self.value = kurt.Color(self.value) + return self + class lparen(Token): lbp = 0 + def nud(self): global token contents = expression() - if isinstance(contents, rparen): # empty brackets + + if isinstance(contents, rparen): # empty brackets return + if not isinstance(token, rparen): raise SyntaxError("Expected bracket to match %s" % self.value) + token = next() + return contents + class rparen(Token): lbp = 0 + def nud(self): return self + class newline(symbol): name = "EOL" lbp = 3 + def nud(self): return [] + def led(self, left): if isinstance(left, kurt.Block): left = [left] + return left + class end_token(Token): name = "EOF" lbp = 0 + def __init__(self): self.value = None + def __repr__(self): return "%s()" % self.__class__.__name__ @@ -115,55 +147,69 @@ def inline_blocks(): if len(block.inserts) == 1 and block.inserts[0].shape == "inline": yield block + def all_the_blocks(): for block in kurt.plugin.Kurt.blocks: done_text = set() + for block in block.conversions: if block.text in done_text: continue + yield block + done_text.add(block.text) + def match_part(part, block_part): if isinstance(block_part, kurt.Insert): return (isinstance(part, (Token, kurt.Block, list))) else: return (block_part.strip() == part) + def blocks_starting_with(parts): suppress_blocks = set(suppress_block_names()) for block in all_the_blocks(): if len(parts) > len(block.parts): continue + if (isinstance(block.parts[0], basestring) and block.parts[0].strip() in suppress_blocks): continue + for (p, bp) in zip(parts, block.parts): if not match_part(p, bp): break else: yield block + def next_block_part(parts): for block in blocks_starting_with(parts): next_part = (block.parts[len(parts)] if len(block.parts) > len(parts) else None) + if isinstance(next_part, basestring): next_part = next_part.strip() + yield next_part + def blocks_by_parts(parts): for block in all_the_blocks(): if len(parts) != len(block.parts): continue + for (p, bp) in zip(parts, block.parts): if not match_part(p, bp): break else: yield block + def block_from_parts(parts): args = [] for part in parts: @@ -211,9 +257,11 @@ def block_from_parts(parts): arg = int(arg) if int(arg) == arg else arg except ValueError: pass + if isinstance(arg, (int, long, float, complex, kurt.Block)): ok = True + if insert.shape in ("readonly-menu", "number-menu"): if (str(arg) in insert.options(context) or isinstance(arg, kurt.Block)): @@ -221,8 +269,10 @@ def block_from_parts(parts): if not ok: failure = "%r doesn't fit %s" % (arg, insert.shape) + if insert.kind: failure += " " + insert.kind + break block_args.append(arg) else: @@ -230,7 +280,9 @@ def block_from_parts(parts): else: throw("Wrong type of arguments to block: %s" % failure, repr(parts)) + class iden(Token): + @property def lbp(self): return PRECEDENCE.get(self.value, 100) @@ -256,7 +308,9 @@ def led(self, left): def parse_block(self, parts): global token + self.parts = parts + while 1: part = self.parse_one_part(parts) if part is not None: @@ -265,6 +319,7 @@ def parse_block(self, parts): parts.append(part) else: block = block_from_parts(parts) + if isinstance(token, end_token): return block @@ -272,11 +327,14 @@ def parse_block(self, parts): if not token.value == "end": throw("Expected 'end' after C mouth") token = next() + return block def parse_one_part(self, parts): global token, next + expect = set(next_block_part(parts)) + if not expect: self.parts = parts throw("Can't find block %r" % parts) @@ -293,12 +351,14 @@ def parse_one_part(self, parts): if token.value in text_segments: part = token.value token = next() + return part for insert in menu_inserts: if token.value in insert.options(context): part = token token = next() + return part if None in expect: @@ -310,7 +370,9 @@ def parse_one_part(self, parts): part = [] else: part = expression(1) + assert isinstance(part, list) + return part if isinstance(token, (newline, end_token)): @@ -344,8 +406,8 @@ def parse_one_part(self, parts): (r"\'([^']+)\'", string), (r'\(', lparen), (r'\)', rparen), -# (r'\<', lparen), -# (r'\>', rparen), + # (r'\<', lparen), + # (r'\>', rparen), (r'\n|\r|\r\n', newline), ]] @@ -362,27 +424,35 @@ def parse_one_part(self, parts): "turn cw": "turn @turnRight", } + def make_block_tokens(): for block in kurt.plugin.Kurt.blocks: for part in block.parts: if isinstance(part, basestring): yield part.strip() + for alias in SEGMENT_ALIASES: yield alias + def make_menu_tokens(): global context + for kind in kurt.Insert.KIND_OPTIONS: - if kind == "broadcast": continue + if kind == "broadcast": + continue + for o in kurt.Insert(None, kind).options(context): yield str(o) + def suppress_block_names(): for block in kurt.plugin.Kurt.blocks: if isinstance(block.parts[0], kurt.Insert): for o in block.parts[0].options(context): yield o + def tokenize(program): block_tokens = sorted(set(make_block_tokens()) | set(make_menu_tokens())) block_tokens.sort(key=len, reverse=True) @@ -390,8 +460,10 @@ def tokenize(program): block_tokens = filter(None, block_tokens) global remain, lineno + remain = program lineno = 1 + while remain: m = WHITESPACE_PAT.match(remain) if m: @@ -406,45 +478,60 @@ def tokenize(program): contents = m.group(1) else: contents = m.group(0).strip() + yield cls(contents) + remain = remain[m.end():] + if cls == newline: lineno += 1 + break else: for value in block_tokens: if remain.startswith(value): after_value = remain[len(value):] + if not after_value or SEPARATOR_PAT.match(after_value[0]): value = SEGMENT_ALIASES.get(value, value) + yield iden(value) + remain = after_value + break else: throw("Unknown token at %r" % remain.split("\n")[0]) - yield end_token() + yield end_token() #-- Parser --# p_input = "" + def expression(rbp=0): global token + t = token token = next() left = t.nud() + if not hasattr(token, "lbp"): throw("Not an operator: %r" % token) + while rbp < token.lbp: t = token token = next() left = t.led(left) + if not hasattr(token, "lbp"): throw("Not an operator: %r" % token) + return left + def parse(program, scriptable): global token, next, context, p_input @@ -455,12 +542,16 @@ def parse(program, scriptable): next = tokenize(program).next token = next() result = expression() + if not isinstance(token, end_token): throw("Expected end of input") + if isinstance(result, kurt.Block): result = [result] + if not isinstance(result, list): throw("Result does not evaluate to a block") + return kurt.Script(result) @@ -476,8 +567,8 @@ def throw(msg, hint=None, expected=None): msg += ". " + hint line = NEWLINE_PAT.split(p_input)[lineno - 1] - offset = len(p_input) - len(remain) #- len(token.value) + offset = len(p_input) - len(remain) # - len(token.value) err = SyntaxError(msg, ('', lineno, offset, line)) err.expected = expected - raise err + raise err diff --git a/src/blocks2raw.py b/src/blocks2raw.py index 15d892d..5239c34 100644 --- a/src/blocks2raw.py +++ b/src/blocks2raw.py @@ -1,9 +1,10 @@ -import kurt +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import kurt import itertools - def strip_text(block): return kurt.BlockType._strip_text(block.text) @@ -28,6 +29,7 @@ def strip_text(block): "midi": "purple", } + def scratchblocks2_definitions(plugin): plugin = kurt.plugin.Kurt.get_plugin(plugin) plugin_blocks = sorted(plugin.blocks, key=lambda b: b and b.category) @@ -35,7 +37,7 @@ def scratchblocks2_definitions(plugin): done_block_text = set() last_category = None for (category, blocks) in itertools.groupby(plugin_blocks, - key=lambda b: b and b.category): + key=lambda b: b and b.category): if category is None or not blocks or 'obsolete' in category: yield continue @@ -46,7 +48,7 @@ def scratchblocks2_definitions(plugin): yield yield "## %s ##" % plugin_category_classes.get(category, category) - for block in blocks: + for block in blocks: if block: if strip_text(block) not in done_block_text: defaults = list(block.defaults) @@ -55,12 +57,14 @@ def scratchblocks2_definitions(plugin): classes = set() if block.shape in ("hat", "cap"): classes.add(block.shape) + if block.has_insert("stack"): classes.add("cstart") + for (match, class_name) in plugin_class_annotations.items(): if match in code: classes.add(class_name) - + for (match, replacement) in plugin_specials.items(): if match in code: code = code.replace(match, replacement) @@ -80,27 +84,33 @@ def scratchblocks2_definitions(plugin): last_category = category + def print_definitions(plugin, butnot=None): butnot = butnot or set() + for line in scratchblocks2_definitions(plugin): if line not in butnot: if line: print line + if not line.startswith("#"): butnot.add(line) else: print + return butnot + def print_all_definitions(): butnot = print_definitions('scratch20') + print print print print '// Obsolete Scratch 1.4 blocks' + print_definitions('scratch14', butnot) if __name__ == "__main__": print_all_definitions() - diff --git a/src/extract_blocks_20.py b/src/extract_blocks_20.py index 48fbc2d..76621e8 100755 --- a/src/extract_blocks_20.py +++ b/src/extract_blocks_20.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python +# -*- coding: utf-8 -*- # Copyright (C) 2012 Tim Radvan # @@ -19,67 +20,118 @@ """Builds kurt/scratch20/commands_src.py. -Uses decompiled Scratch 2.0 SWF source from showmycode.com. +Uses source code of Scratch 2.0 from GitHub: + + https://github.com/LLK/scratch-flash/blob/96a2a9a7fca2d042da25dc0d4423900163ab4f33/src/Specs.as + +Original source code (development): + + https://github.com/LLK/scratch-flash/blob/develop/src/Specs.as + +ScratchX source code (with extension): + + https://github.com/LLK/scratch-flash/blob/scratchx/src/Specs.as """ import os +import urllib -def relpath(path): - return os.path.join(os.path.dirname(__file__), path) +SCRATCH_URL = "https://raw.githubusercontent.com/LLK/scratch-flash/96a2a9a7fca2d042da25dc0d4423900163ab4f33/src/Specs.as" +SCRATCHX_URL = "https://raw.githubusercontent.com/LLK/scratch-flash/scratchx/src/Specs.as" -# Strip weird chars -f = open(relpath("showmycode.com.txt")) -contents = f.read() -f.close() +EXPERIMENTAL = True -contents = contents.replace("\r\n", "\n").replace("\xef\xbb\xbf","") +if not EXPERIMENTAL: + target_url = SCRATCH_URL +else: + target_url = SCRATCHX_URL -f = open(relpath("showmycode.com.txt"), "w") -f.write(contents) -f.close() +# get file from repository (raw url) +contents = urllib.urlopen(target_url).read() -# Split lines +# split lines lines = contents.split("\n") -out = "# Generated from Scratch SWF source by src/extract_blocks.py\n" +out = "\ +#!/usr/bin/env python\n\ +# -*- coding: utf-8 -*-\n\ +\n\ +# Generated by src/extract_blocks_20.py\n" -def add_line_at_index(list_name, i, comment=None): +def relpath(path): + """ + Returns the complete path of a partial file given. + + @param path: The incomplete path of the file. + @return: The string with the complete path in the OS. + """ + + return os.path.join(os.path.dirname(__file__), path) + + +def add_commands(list_name, commands, comment=None): + """ + Function to generate a pythonized string with the commands from the original + source code of Scratch. + + @param list_name: The name of the list variable that contains the commands. + @param commands: The list per se. + @param comment: The initial comment to clarify the code. Default: None. + """ + global out - line = lines[i] - out += "\n" - # Comment with function definition - if not comment: - defun = "public static function" - while i > 0 and not lines[i].lstrip().startswith(defun): - i -= 1 - comment = lines[i].lstrip() - comment = comment[len(defun):].strip().rstrip("{") + out += "\n" out += "# %s\n" % comment - # Strip array code - line = line[line.index("["):] - line = line.strip().rstrip(";") - - # Write commands array + # write commands array if "%s = [" % list_name in out: out += "%s += [\n" % list_name else: out += "%s = [\n" % list_name - for cmd in eval(line): - out += " %r,\n" % cmd - out += "]\n" + # decompose string for extension (optional) + if "extension" in comment: + out += "# add extensions code if not auto-generated\n" + + # eval each line + for idx in range(len(commands)): + try: + for cmd in eval(commands[idx]): + out += " %r,\n" % cmd + except: + pass + + out += "]\n" -for (i, line) in enumerate(lines): - if "commands:Array" in line: - add_line_at_index("commands", i, "commands:Array") - if "_local1.blockSpecs" in line: - add_line_at_index("extras", i) -f = open(relpath("../kurt/scratch20/commands_src.py"), "w") -f.write(out) -f.close() +if __name__ == "__main__": + # search the index in the original source code + extensions = False + for (i, line) in enumerate(lines): + if "commands:Array" in line: + line_commands = i + line_extensions = len(lines) + + for (j, line) in enumerate(lines): + if "extensionSpecs:Array" in line: + extensions = True + line_extensions = j + break + break + + # set the code for the general commands (and extensions) + add_commands("commands", + lines[line_commands:line_extensions], "commands:Array") + + if extensions: + add_commands("extras", + lines[line_extensions:], "extension:Array") + + # write the new code generated + f = open(relpath("../kurt/scratch20/commands_src.py"), "w") + f.write(out) + f.close() diff --git a/src/measure_block.py b/src/measure_block.py index de71ea2..3015607 100644 --- a/src/measure_block.py +++ b/src/measure_block.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """Script for extracting block heights from Scratch 1.4. Requires the following change to the Scratch source code: @@ -7,6 +10,7 @@ Added to the end of Scratch-UI-Panes -> ScratchFrameMorph -> file read/write -> installNewProject: """ + import os import sys @@ -18,11 +22,9 @@ sys.path.insert(0, path_to_lib) import kurt -import heights from heights import bheights - def measure_height(blocks): PATH = "tests/measure.sb" @@ -35,12 +37,13 @@ def measure_height(blocks): s = p.stage s.scripts.append(kurt.Script(blocks)) s.scripts.append(kurt.Script([kurt.Block("say")])) - #p.sprites.append(s) + # p.sprites.append(s) p.convert("scratch14") p.save(PATH) mtime = os.stat(PATH).st_mtime os.system("open %s" % PATH) + while 1: try: if os.stat(PATH).st_mtime != mtime: @@ -54,8 +57,8 @@ def measure_height(blocks): (x1, y1) = scripts[0].pos (x2, y2) = scripts[1].pos height = y2 - y1 - 15 - return height + return height blacklist = set([ @@ -66,7 +69,7 @@ def measure_height(blocks): ]) -#for bt in kurt.plugin.Kurt.blocks: +# for bt in kurt.plugin.Kurt.blocks: # if "scratch14" in bt.conversions: # command = bt.convert("scratch14").command # if command in bheights or command in blacklist: