Our job is to make your job easier

Documentation

Examples

Community

RapydScript (pronounced 'RapidScript') is a pre-compiler for JavaScript, similar to CoffeeScript, but with cleaner, more readable syntax. The syntax is very similar to Python, but allows JavaScript as well. This project was written as an alternative to Pyjamas for those wishing Python-like JavaScript without the extra overhead and complexity Pyjamas introduces.

Live Demos for every example can be found in the RapydScript Showcase

You can download RapydScript from our repository: https://github.com/atsepkov/RapydScript

RapydScript

What is RapydScript?

RapydScript (pronounced 'RapidScript') is a pre-compiler for JavaScript, similar to CoffeeScript, but with cleaner, more readable syntax. The syntax is very similar to Python, but allows JavaScript as well. This project was written as an alternative to Pyjamas for those wishing Python-like JavaScript without the extra overhead and complexity Pyjamas introduces.

RapydScript allows to write your front-end in Python without the overhead that other similar frameworks introduce (the performance is the same as with pure JavaScript). To those familiar with PyvaScript, the best way to describe RapydScript is PyvaScript++. To those familiar with CoffeeScript, RapydScript is like CoffeeScript with syntax (and some features) of Python. To those familiar with Pyjamas, RapydScript brings many of the same features and support for Python syntax without the same overhead. Don't worry if you've never used either of the above-mentioned compilers, if you've ever had to write your code in pure JavaScript you'll appreciate RapydScript. RapydScript combines the best features of Python as well as JavaScript, bringing you features most other Pythonic JavaScript replacements overlook. Here are a few features of RapydScript:

  • classes that work and feel similar to Python
  • modules that can be used for logic abstraction and allow more flexibility than Python's modules
  • optional function arguments that work similar to Python
  • inheritance system that's both, more powerful than Python and cleaner than JavaScript
  • support for object literals with anonymous functions, like in JavaScript
  • ability to invoke any JavaScript/DOM object/function/method as if it's part of the same framework, without the need for special syntax
  • variable and object scoping that make sense (no need for repetitive 'var' or 'new' keywords)
  • ability to use both, Python's methods/functions and JavaScript's alternatives
  • similar to above, ability to use both, Python's and JavaScript's tutorials (as well as widgets)
  • it's self-hosting, that means the compiler is itself written in RapydScript and compiles into JavaScript

Let's not waste any more time with the introductions, however. The best way to learn a new language/framework is to dive in.

Community

If you have questions, bug reports, or feature requests, feel free to post them on our mailing list:
http://groups.google.com/group/rapydscript

I bundled a few demos with RapydScript itself, but several members of the community put together much better demos themselves. If you would like to take a look at them to see what's possible with RapydScript, here are some examples:

http://salvatore.pythonanywhere.com/RapydScript
This includes the demos from RapydScript's examples directory, as well as a few others.

http://salvatore.pythonanywhere.com/RapydBox
This is a collection of very cool demos, showcasing RapydScript's similarity to real Python and at the same time its ability to work with other JavaScript. It relies on a JavaScript port of NodeBox (which was originally written in Python). NodeBox was ported from Python to JavaScript to allow cross-platform compatibility. Ironically, the original demos from Python version of NodeBox now work with JavaScript version of NodeBox with few changes (and sometimes none at all) by using RapydScript.

http://salvatore.pythonanywhere.com/RapydGlow RapydScript making use of GlowScript, another project done by a member of our community

Installation

First make sure you have installed the latest version of node.js (You may need to restart your computer after this step). You may also need to install optimist library.

From NPM for use as a command line app:

npm install rapydscript -g

From NPM for programmatic use:

npm install rapydscript

From Git:

git clone git://github.com/atsepkov/RapydScript.git
cd RapydScript
npm link .

If you're using OSX, you can probably use the same commands (let me know if that's not the case). If you're using Windows, you should be able to follow similar commands after installing node.js and git on your system.

Compilation

Once you have installed RapydScript, compiling your application is as simple as running the following command:

rapydscript <location of main file> [options]

By default this will dump the output to STDOUT, but you can specify the output file using --output option. The generated file can then be referenced in your html page the same way as you would with a typical JavaScript file. If you're only using RapydScript for classes and functions, then you're all set. If you're using additional Python methods, such as range(), print(), list.append(), list.remove(), then you will want to link RapydScript's stdlib.js in your html page as well. There are two ways of doing this, one is to include it as a JavaScript file in your HTML, the other is to include it as an import in your source code and let RapydScript pull it in automatically.

RapydScript2 can take multiple input files. It's recommended that you pass the input files first, then pass the options. RapydScript will parse input files in sequence and apply any compression options. The files are parsed in the same global scope, that is, a reference from a file to some variable/function declared in another file will be matched properly.

If you want to read from STDIN instead, pass a single dash instead of input files.

The available options are:

-o, --output       Output file (default STDOUT).
-b, --bare         Omit scope-protection wrapper around generated code
-p, --prettify     Beautify output/specify output options.            [string]
-n, --namespace-imports  Pythonic imports (experimental)
-v, --verbose      Verbose                                           [boolean]
-V, --version      Print version number and exit.                    [boolean]
-t, --test         Run unit tests, making sure the compiler produces usable code
-m, --omit-baselib Omit base library from generated code, make sure you're including baselib.js if you use this
-i, --auto-bind    Automatically bind methods to the class they belong to (more Pythonic, but could interfere with other JS libs)
--screw-ie8        Optimize compilation, sacrificing compatibility with older browsers

The rest of the option remain from UglifyJS and have not been tested, some may work, but most will not, since the AST is different between RapydScript and UglifyJS. These option will eventually be removed or modified to be relevant to RapydScript.

Getting Started

Like JavaScript, RapydScript can be used to create anything from a quick function to a complex web-app. RapydScript can access anything regular JavaScript can, in the same manner. Let's say we want to write a function that greets us with a "Hello World" pop-up. The following code will do it:

def greet():
    alert("Hello World!")

Once compiled, the above code will turn into the following JavaScript:

function greet() {
    alert("Hello World!");
}

Now you can reference this function from other JavaScript or the page itself (using "onclick", for example). For our next example, let's say you want a function that computes factorial of a number:

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n-1)

Now all we need is to tie it into our page so that it's interactive. Let's add an input field to the page body and a cell for displaying the factorial of the number in the input once the input loses focus.

<input id="user-input" onblur="computeFactorial()"></input>
<div id="result"></div>

NOTE: To complement RapydScript, I have also written RapydML (http://bitbucket.org/pyjeon/rapydml), which is a pre-compiler for HTML (just like RapydScript is a pre-compiler for JavaScript).

Now let's implement computeFactorial() function in RapydScript:

def computeFactorial():
    n = document.getElementById("user-input").value
    document.getElementById("result").innerHTML = factorial(n)

Again, notice that we have access to everything JavaScript has access to, including direct DOM manipulation. Once compiled, this function will look like this:

function computeFactorial() {
    var n;
    n = document.getElementById("user-input").value;
    document.getElementById("result").innerHTML = factorial(n);
}

Notice that RapydScript automatically declares variables in local scope when you try to assign to them. This not only makes your code shorter, but saves you from making common JavaScript mistake of overwriting a global. For more information on controlling variable scope, see Scope Control section.

Leveraging other APIs

Aside from Python-like stdlib, RapydScript does not have any of its own APIs. Nor does it need to, there are already good options available that we can leverage instead. If we wanted, for example, to rewrite the above factorial logic using jQuery, we could easily do so:

def computeFactorial():
    n = $("#user-input").val()
    $("#result").text(factorial(n))

Many of these external APIs, however, take object literals as input. Like with JavaScript, you can easily create those with RapydScript, the same way you would create one in JavaScript, or a dictionary in Python:

styles = {
    'background-color': '#ffe',
    'border-left':      '5px solid #ccc',
    'width':            50,
}

Now you can pass it to jQuery:

$('#element').css(styles)

Another feature of RapydScript is ability to have functions as part of your object literal. JavaScript APIs often take callback/handler functions as part of their input parameters, and RapydScript lets you create such object literal without any quirks/hacks:

params = {
    width:  50,
    height: 30,
    onclick:    def(event):
        alert("you clicked me"),
    onmouseover:    def(event):
        $(this).css('background', 'red')
    ,
    onmouseout: def(event):
        # reset the background
        $(this).css('background', '')
}

Note the comma on a new line following a function declaration, it needs to be there to let the compiler know there are more attributes in this object literal, yet it can't go on the same line as the function since it would get parsed as part of the function block. Like Python, however, RapydScript supports new-line shorthand using a ;, which you could use to place the comma on the same line:

hash = {
    'foo':  def():
        print('foo');,
    'bar':  def():
        print('bar')
}

It is because of easy integration with JavaScript's native libraries that RapydScript keeps its own libraries to a minimum. For example, it does not implement string interpolation, like native Python. However, by using sprintf.js library (https://github.com/alexei/sprintf.js) you can reproduce the same behavior in RapydScript:

string = vsprintf('%d bottles of %s on the wall', (99, 'beer'))

Take a look at the examples directory to see RapydScript integration with jQuery, jQuery-UI, D3, and Google Charts.

Anonymous Functions

Like JavaScript, RapydScript allows the use of anonymous functions. In fact, you've already seen the use of anonymous functions in previous section when creating an object literal ('onmouseover' and 'onmouseout' assignments). This is similar to Python's lambda function, except that the syntax isn't awkward like lambda, and the function isn't limited to one line. The following two function declarations are equivalent:

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n-1)

factorial = def(n):
    if n == 0:
        return 1
    return n * factorial(n-1)

This might not seem like much at first, but if you're familiar with JavaScript, you know that this can be extermely useful to the programmer, especially when dealing with nested functions, which are a bit syntactically awkward in Python (it's not immediatelly obvious that those can be copied and assigned to other objects). To illustrate the usefulness, let's create a method that creates and returns an element that changes color while the user keeps the mouse pressed on it.

def makeDivThatTurnsGreen():
    div = $('<div></div>')
    turnGreen = def(event):
        div.css('background', 'green')
    div.mousedown(turnGreen)
    resetColor = def(event):
        div.css('background', '')
    div.mouseup(resetColor)
    return div

At first glance, anonymous functions might not seem that useful. We could have easily created nested functions and assigned them instead. By using anonymous functions, however, we can quickly identify that these functions will be bound to a different object. They belong to the div, not the main function that created them, nor the logic that invoked it. The best use case for these is creating an element inside another function/object without getting confused which object the function belongs to.

Additionally, as you already noticed in the previous section, anonymous functions can be used to avoid creating excessive temporary variables and make your code cleaner:

math_ops = {
    add:    def(a, b): return a+b;,
    sub:    def(a, b): return a-b;,
    mul:    def(a, b): return a*b;,
    div:    def(a, b): return a/b;,
    roots:  def(a, b, c):
        r = Math.sqrt(b*b - 4*a*c)
        d = 2*a
        return (-b + r)/d, (-b - r)/d
}

I'm sure you will agree that the above code is cleaner than declaring 5 temporary variables first and assigning them to the object literal keys after. Note that the example puts the function header (def()) and content on the same line. I'll refer to it as function inlining. This is meant as a feature of RapydScript to make the code cleaner in cases like the example above. While you can use it in longer functions by chaining statements together using ;, a good rule of thumb (to keep your code clean) is if your function needs semi-colons ask yourself whether you should be inlining, and if it needs more than 2 semi-colons, the answer is probably no (note that you can also use semi-colons as newline separators within functions that aren't inlined, as in the example in the previous section).

Decorators

Like Python, RapydScript supports function decorators. While decorator arguments are not supported, the basic decorators work exactly the same way as in Python:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

hello() # returns "<b><i>hello world</i></b>"

Chaining Blocks

RapydScript wouldn't be useful if it required work-arounds for things that JavaScript handled easily. If you've worked with JavaScript or jQuery before, you've probably seen the following syntax:

function(){
    // some logic here
}.call(this);

This code calls the function immediatelly after declaring it instead of assigning it to a variable. Python doesn't have any way of doing this. The closest work-around is this:

def tmp():
    # some logic here
tmp.__call__()

While it's not horrible, it did litter our namespace with a temporary variable. If we have to do this repeatedly, this pattern does get annoying. This is where RapydScript decided to be a little unorthodox and implement the JavaScript-like solution:

def():
    # some logic here
.call(this)

RapydScript will bind any lines beginning with . to the outside of the block with the matching indentation. This logic isn't limited to the .call() method, you can use it with .apply() or any other method/property the function has assigned to it. This can be used for jQuery as well:

$(element)
.css('background-color', 'red')
.show()

The only limitation is that the indentation has to match, if you prefer to indent your chained calls, you can still do so by using the \ delimiter:

$(element)\
    .css('background-color', 'red')\
    .show()

RapydScript also allows an alternative syntax for the same feature, for those prefering Python's traditional, hanging-indent look:

def(one, two) and call(this, 1, 2):
    ...

Which is equivalent to the following:

def(one, two):
    ...
.call(this, 1, 2)

Some of you might welcome this feature, some of you might not. RapydScript always aims to make its unique features unobtrusive to regular Python, which means that you don't have to use them if you disagree with them. Recently, we have enhanced this feature to handle do/while loops as well:

a = 0
do:
    print(a)
    a += 1
.while a < 1

In my opinion, this is something even Python could benefit from. Like with functions, you could use the hanging-indent form as well:

a = 0
do and while a < 1:
    print(a)
    a += 1

Optional Arguments

Like Python, Javascript allows optional arguments to be passed into a function. Unlike Python, however, JavaScript doesn't assign default values to these, nor does it work well with optional arguments. If you forget to pass an argument, JavaScript will simply set the variable to undefined, and it's up to you to handle it. Should you forget about it, you'll probably pay the price later, when you use this variable in any sort of mathematical computation. By that time, you'll either end up with NaN (not a number), infinity, or some other weird value, causing an error a few hundred lines after the line responsible for causing it (good luck debugging it). Luckily, RapydScript allows sane optional arguments, similar to Python. The following function, for example, allows you to pass in a color for the background, or it will default to black (the format is r,g,b,a):

def setColor(color=[0,0,0,1]):
    $('body').css('background', 'rgba('+','.join(color)+')')

One thing to note here, is that unlike Python, RapydScript will create a separate object for the optional argument each time the function is called. This makes it slightly less efficient, but prevents it from messing up existing objects.

Variable number of arguments (*args)

Like Python, Javascript allows the user to write a function that takes variable number of arguments and performs its logic on all of them. Some examples of this are Math.max(), console.log() function and array's concat() method. Unlike, Python, however, JavaScript does not make this intuitive. You have to use a special iterable element inside the function called arguments, which has some properties of an array but doesn't support all of the functionality. Likewise, if you want to unfold an array into a list of arguments for a function during a function call, you have to use .apply() method instead of invoking the function normally. RapydScript converts Pythonic way of *args declaration to JavaScript. For example, the following function definition will take 2 named arguments, and dump the rest into an array (an actual array, not a gimped arguments object, like in JavaScript):

def doSomething(a, b, *args):
    ...

Likewise, the following function call will unpack the array into separate arguments for the function:

doSomething(*args)

This is useful for cases where you have multiple elements inside a list, but don't know the size of this list. Here are two equivalent ways of calling the print function, for example:

# first way
a = 'I was here'
b = 'and there'
print(a, b)

# second way
a = 'I was here'
b = 'and there'
args = [a, b]
print(*args)

In this particular case, there is no advantage to using *, but if args gets passed in from outside, and we don't know its size, * becomes very handy. Likewise, if you're writing a function that can take variable number of arguments, it's cleaner to use *args rather than forcing the developer using it to pass in an array.

Keyword Arguments (**kwargs)

RapydScript implementation of kwargs is not completely consistent with Python, this is to minimize overhead since 99% of the functions you write will not need this feature. First, let's cover function calls. The following calls are equivalent:

test('baz', foo=1, 99, bar=3)
test('baz', 99, {foo: 1, bar: 3})

The first function call may be more convenient to those who spend a lot of time in Python. As far as the function itself is concerned, both of these just collect named arguments in a hash and pass them in the last argument. This means that a regular function in RapydScript will not see these arguments unless you explicitly tell it that they are in the last argument:

def test(foo, bar, kw):
    print(foo)  # 'baz'
    print(bar)  # 99
    print(kw)   # {foo: 1. bar: 3}
    print(kw.foo)   # 1

Luckily, there is a kwargs decorator in stdlib that will simulate the expected Python behavior:

@kwargs
def test(foo, bar, kw):
    print(foo)  # 1
    print(bar)  # 3
    print(kw)   # {foo: 1. bar: 3}
    print(kw.foo)   # 1

Inferred Tuple Packing/Unpacking

Like Python, RapydScript allows inferred tuple packing/unpacking and assignment. While inferred/implicit logic is usually bad, it can sometimes make the code cleaner, and based on the order of statements in the Zen of Python, 'beautiful' takes priority over 'explicit'. For example, if you wanted to swap two variables, the following looks cleaner than explicitly declaring a temporary variable:

a, b = b, a

Likewise, if a function returns multiple variables, it's cleaner to say:

a, b, c = fun()

rather than:

tmp = fun()
a = tmp[0]
b = tmp[1]
c = tmp[2]

Since JavaScript doesn't have tuples, RapydScript uses arrays for tuple packing/unpacking behind the scenes, but the functionality stays the same. Note that unpacking only occurs when you're assigning to multiple arguments:

a, b, c = fun()     # gets unpacked
tmp = fun()         # no unpacking, tmp will store an array of length 3

Unpacking can also be done in for loops (which you can read about in later section):

for index, value in enumerate(items):
    print(index+': '+value)

Tuple packing is the reverse operation, and is done to the variables being assigned, rather than the ones being assigned to. This can occur during assignment or function return:

def fun():
    return 1, 2, 3

To summarize packing and unpacking, it's basically just syntax sugar to remove obvious assignment logic that would just litter the code. For example, the swap operation shown in the beginning of this section is equivalent to the following code:

tmp = [b, a]
a = tmp[0]
b = tmp[1]

Python vs JavaScript

RapydScript allows you to use both, Python and JavaScript names for the methods. For example, we can 'push()' a value to array, as well as 'append()' it:

arr = []
arr.push(2)
arr.append(4)
print(arr) # outputs [2,4]

In order to use Python's methods, you will have to include RapydScript's stdlib.js. There are two ways of doing this. One is by adding the following line in your html page:

<script type="text/javascript" src='stdlib.js'></script>

The other way is to include the following line at the top of your main RapydScript file:

import stdlib

The advantage of the second method is that the library will automatically be pulled in without having to manually copy it over into your JavaScript directory. The first include method, however, might be useful when you have multiple independently compiled RapydScript programs on your page and don't want the overhead of the same stdlib included in each one.

In order to simulate Python-like methods, I had to bastardize a couple of JavaScript's own methods. For example, array.pop() has been overwritten to work like Python's pop() (or JavaScript's splice()):

arr.pop()       # removes last element (expected behavior in JavaScript and Python)
arr.pop(2)      # removes third element (expected behavior in Python, but not JavaScript)
arr.splice(2,1) # removes third element (expected behavior in JavaScript, but not Python)

There is a subtle difference between the last 2 lines above, arr.pop(2) will return a single element, arr.splice(2,1) will return an array containing that single element. Most of the keywords are interchangeable as well:

RapydScript     JavaScript

None/null       null
False/false     false
True/true       true
undefined       undefined
this            this

The JavaScript operators, however, are not supported. You will have to use Python versions of those. If you're unfamiliar with them, here is the mapping RapydScript uses:

RapydScript     JavaScript

and             &&
or              ||
not             !
is              ===
is not          !==
+=1             ++
-=1             --

Admittedly, is is not exactly the same thing in Python as === in JavaScript, but JavaScript is quirky when it comes to comparing objects anyway.

You may also be interested in deep_eq function, which is part of stdlib. It performs deep equality test on two objects, and works on any types, including hashes and arrays:

deep_eq([1,2,3], [1,[2,3]])     # False
deep_eq([[1,2],3], [1,[2,3]])   # False
deep_eq([1,[2,3]], [1,[2,3]])   # True

In rare cases RapydScript might not allow you to do what you need to, and you need access to pure JavaScript. When that's the case, you can wrap your JavaScript in a string, passing it to JS() method. Code inside JS() method is not a sandbox, you can still interact with it from normal RapydScript:

JS('a = {foo: "bar", baz: 1};')
print(a.foo)    # prints "bar"

One last thing to note is the difference between print() and console.log, print() is part of RapydScript's stdlib, and is designed to work similar to Python's print statement, console.log() is JavaScript's version of debug output, which is more powerful but also more tedious when you just want a quick output. Here are some examples:

arr = [1,2,3,4]
print(arr)          # [1,2,3,4]
console.log(arr)    # [1,2,3,4]

arr2 = [[1,2],[3,4]]
print(arr2)         # [[1,2],[3,4]]
console.log(arr2)   # [Array[2], Array[2]]

hash = {'dogs': 1, 'cats': 2}
print(hash)         # {"dogs":1,"cats":2}
console.log(hash)   # Object

One other topic I debated for a while is handling of conditionals. In Python, if you wanted a one-liner conditional that gets assigned to a variable, you would write something like this:

foo = bar if baz else 10

In JavaScript, the equivalent logic would be written as follows:

var foo = baz ? bar : 10

Which one looks cleaner is subject to personal preference (I'm used to seeing condition first in most of the if statements, so the second way makes more sense to me). But where the second approach wins is when dealing with anonymous functions. Indeed, Python doesn't have to handle them, yet RapydScript does. As a result, I decided to go with JavaScript's approach here. This also allows me to assign functions here, same way as JavaScript would:

foo = baz ? def(): return bar; : def(): return 10

Loops

RapydScript's loops work like Python, not JavaScript. You can't, for example use 'for(i=0;i<max;i++)' syntax. You can, however, loop through arrays using 'for ... in' syntax without worrying about the extra irrelevant attributes regular JavaScript returns.

animals = ['cat', 'dog', 'mouse', 'horse']
for animal in animals:
    print('I have a '+animal)

If you need to use the index in the loop as well, you can do so by using enumerate():

for index, animal in enumerate(animals):
    print("index:"+index, "animal:"+animal)

Like in Python, if you just want the index, you can use range:

for index in range(len(animals)):           # or range(animals.length)
    print("animal "+index+" is a "+animals[index])

When possible, RapydScript will automatically optimize the loop for you into JavaScript's basic syntax, so you're not missing much by not being able to call it directly.

List Comprehensions

RapydScript also supports list comprehensions, using Python syntax. Instead of the following, for example:

myArray = []
for index in range(1,20):
    if index*index % 3 == 0:
        myArray.append(index*index)

You could write this:

myArray = [i*i for i in range(1,20) if i*i%3 == 0]

NOTE: The rest of this section has been removed since the quirks it mentions have not been relevant since the old, Python-based compiler. Current implementation works just as well as a normal loop.

Inclusive/Exclusive Sequences

Like Python, RapydScript has a range() function. While powerful, the result it generates isn't immediately obvious when looking at the code. It's a minor pet peeve, but the couple extra seconds trying to visually parse it and remember that it's not inclusive can detract from the code flow. To remedy this, RapydScript borrows to/til operators from LiveScript (also known as human-readable versions of Ruby's ../...). The following 4 lines of code are equivalent, for example:

a = [3 to 8]
a = [3 til 9]
a = range(3, 9)
a = [3, 4, 5, 6, 7, 8]

You can also use sequences within loops:

for i in [1 to 5]:
    print(i)

Or in list comprehensions:

[i*i for i in [1 to 6] if i%2 == 0]

The to/til statement gets converted to range() at compile time, and therefore can support variables or even expressions for start and end of the range:

num = 5
rng = [num to num * 2]

The to/til operators bind less tightly than arithmetic operators, so parentheses are optional.

Classes

This is where RapydScript really starts to shine. JavaScript is known for having really crappy class implementation (it's basically a hack on top of a normal function, most experienced users suggest using external libraries for creating those instead of creating them in pure JavaScript). Luckily RapydScript fixes that. Let's imagine we want a special text field that takes in a user color string and changes color based on it. Let's create such field via a class.

class ColorfulTextField:
    def __init__(self):
        field = $('<input></input>')
        changeColor = def(event):
            field.css('backround', field.val())
        field.keydown(changeColor)
        self.widget = field

This class abuses DOM's tolerant behavior, where it will default to the original setting when the passed-in color is invalid (saving us the extra error-checking logic). To append this field to our page we can run the following code:

textfield = ColorfulTextField()
$('body').append(textfield.widget)

If you're used to JavaScript, the code above probably set off a few red flags in your head. In pure JavaScript, you can't create an object without using a 'new' operator. Don't worry, the above code will compile to the following:

var textfield;
textfield = new ColorfulTextField()
$('body').append(textfield.widget);

RapydScript will automatically handle appending the 'new' keyword for you, assuming you used 'class' to create the class for your object. This also holds when creating an object inside a list or returning it as well. You could easily do the following, for example:

fields = [ColorfulTextField(), ColorfulTextField(), ColorfulTextField()]

This is very useful for avoiding a common JavaScript error of creating 'undefined' objects by forgetting this keyword. One other point to note here is that regular DOM/JavaScript objects are also covered by this. So if you want to create a DOM image element, you should not use the 'new' keyword either:

myImage = Image()

But RapydScript's capability doesn't end here. Like Python, RapydScript allows inheritance. Let's say, for example, we want a new field, which works similar to the one above. But in addition to changing color of the field, it allows us to change the color of a different item, with ID of 'target' after we press the 'apply' button, located right next to it. Not a problem, let's implement this guy:

class TextFieldAffectingOthers(ColorfulTextField):
    def __init__(self):
        ColorfulTextField.__init__(self)
        field = self.widget
        submit = $('<button type="button">apply</button>')
        applyColor = def(event):
            $('#target').css('background', field.val())
        submit.click(applyColor)
        self.widget = $('<div></div>')\
            .append(field)\
            .append(submit)

A couple things to note here. We can invoke methods from the parent class the same way we would in Python, by using Parent.method(self, ...) syntax. This allows us to control when and how (assuming it requires additional arguments) the parent method gets executed. Also note the use of \ operator to break up a line. This is something Python allows for keeping each line short and legible. Likewise, RapydScript, being indentation-based, allows the same.

An important distinction between Python and RapydScript is that RapydScript does not allow multiple inheritance. This might seem like a big deal at first, but in actuality it barely matters. When using multiple inheritance, we usually only care about a few methods from the alternative classes. Leveraging JavaScript prototypical inheritance, RapydScript allows us to reuse methods from another class without even inheriting from it:

class Something(Parent):
    def method(self, var):
        Parent.method(self, var)
        SomethingElse.method(self, var)
        SomethingElse.anotherMethod(self)

Notice that Something class has no __init__ method. Like in Python, this method is optional for classes. If you omit it, an empty constructor will automatically get created for you by RapydScript (or when inheriting, the parent's constructor will be used). Also notice that we never inherited from SomethingElse class, yet we can invoke its methods. This brings us to the next point, the only real advantage of inheriting from another class (which you can't gain by calling the other classes method as shown above) is that the omitted methods are automatically copied from the parent. Admittedly, we might also care about isinstance() method, to have it work with the non-main parent, which is equivalent to JavaScript's instanceof operator.

NOTE: If you compile your code without --screw-ie8 flag, the constructor will be executed every time you create a subclass of the given class. In most cases this will not hurt you, but in some cases you could see odd side-effects. For example, having a print() statement in the parent constructor will cause its contents to be output for every subclass, even if you do not explicitly create an instance. --screw-ie8 option fixes this issue, at the expense of compatibility with Internet Explorer 6-8.

There is also a convenience function in RapydScript called mixin that lets you assign all methods of a given class to another, like Python's multiple inheritance:

mixin(Snake, Animal, false)     # add Animal's methods to Snake, don't overwrite ones already declared in Snake
mixin(Snake, Animal, true)      # add Animal's methods to Snake, overwriting ones already declared in Snake

To summarize classes, assume they work the same way as in Python, plus a few bonus cases. The following, for example, are equivalent:

class Aclass:
    def __init__(self):
        pass

    def method(self):
        doSomething(self)

class Aclass:
    def __init__(self):
        self.method = def():
            doSomething(self)

The variable self in the above example is not a keyword. Like in Python, it can be replaced with any other variable name. Also, like in Python, this variable will be tied to the class, unlike this keyword of JavaScript. RapydScript still treats this keyword the same way JavaScript does:

class Main:
    def __init__(s):
        main = this
        method = def():
            main.doSomething()
        $('#element').click(method)

    def doSomething(s):
        ...

Or, leveraging Pythonic binding to first argument, the same can be shortened to:

class Main:
    def __init__(s):
        method = def():
            s.doSomething()
        $('#element').click(method)

    def doSomething(s):
        ...

Like Python, RapydScript allows static methods. Marking the method static with @staticmethod decorator will compile that method such that it's not bound to the object instance, and ensure all calls to this method compile into static method calls:

class Test:
    def normalMethod(self):
        return 1

    @staticmethod
    def staticMethod(a):
        return a+1

Some methods in the native JavaScript classes, such as String.fromCharCode() have also been marked as static to make things easier for the developer.

External Classes

RapydScript will automatically detect classes declared within the same scope (as long as the declaration occurs before use), as well as classes properly imported into the module (each module making use of a certain class should explicitly import the module containing that class). RapydScript will also properly detect native JavaScript classes (String, Array, Date, etc.). Unfortunately, RapydScript has no way of detecting classes from third-party libraries. In those cases, you could use the new keyword every time you create an object from such class. Alternatively, you could mark the class as external.

Marking a class as external is done via external decorator. You do not need to fill in the contents of the class, a simple pass statement will do:

@external
class Alpha:
    pass

RapydScript will now treat Alpha as if it was declared within the same scope, auto-prepending the new keyword when needed and using prototype to access its methods (see casperjs example in next section to see how this can be used in practice). You don't need to pre-declare the methods of this class (unless you decide to for personal reference, the compiler will simply ignore them) unless you want to mark certain methods as static:

@external
class Alpha:
    @staticmethod
    def one():
        pass

Alpha.one is now a static method, every other method invoked on Alpha will still be treated as a regular class method. While not mandatory, you could pre-declare other methods you plan to use from Alpha class as well, to make your code easier to read for other developers, in which case this external declaration would also serve as a table of contents for Alpha:

@external
class Alpha:
    def two(): pass
    def three(): pass

    @staticmethod
    def one(): pass

As mentioned earlier, this is simply for making your code easier to read. The compiler itself will ignore all method declarations except ones marked with staticmethod decorator.

You could also use external decorator to bypass improperly imported RapydScript modules. However, if you actually have control of these modules, the better solution would be to fix those imports.

Method Binding

By default, RapydScript does not bind methods to the classes they're declared under. This behavior is unlike Python, but very much like the rest of JavaScript. For example, consider this code:

class Boy:
    def __init__(self, name):
        self.name = name

    def greet(self):
        print('My name is' + self.name)

tod = Boy('Tod')
tod.greet()                 # Hello, my name is Tod
getattr(tod, 'greet')()     # Hello, my name is undefined

In some cases, however, you may wish for the functions to remain bound to the object they were retrieved from. For those cases, RapydScript has bind function. Unlike regular JavaScript Function.prototype.bind, RapydScript's bind can rebind methods that have already been bound. The binding we wanted to see in the above example can be achieved as follows:

bound = bind(getattr(tod, 'greet'), tod)
bound()                     # Hello, my name is Tod

To unbind a bound method, you can call pass false as a second argument instead of an object you wish to bind to. You can also auto-bind all methods of the class by calling rebind_all:

class Boy:
    def __init__(self, name):
        self.name = name
        rebind_all(self)

    def greet(self):
        print('My name is' + self.name)

tod = Boy('Tod')
tod.greet()                 # Hello, my name is Tod
getattr(tod, 'greet')()     # Hello, my name is Tod

Likewise, rebind_all(self, false) will unbind all methods. It's not recommended to auto-bind classes that inherit from 3rd party libraries. For example, casperjs has Casper class, which RapydScript can easily inherit and extend:

@external
class Casper:
    pass

class Scraper(Casper):
    def __init__(self):
        Casper.__init__(self)
        self.start()

s = Scraper()
s.thenOpen('http://casperjs.org',
    def(): this.echo(this.getTitle())
)
s.run()

Including rebind_all call in the constructor, however, will break Casper. It is for that reason that rebind_all isn't added to the constructor by default by RapydScript. You could, however use --auto-bind compile flag to have RapydScript rebind automatically for you. There is a bit more that this flag does behind the scenes, which ensures that class binding behaves identical to Python, at the expense of some performance and compatibility with libraries like casperjs.

Modules

Unlike Python, RapydScript's import system does not automatically encapsulate external files in modules. Multiple RapydScript users have raised concerns about this, so I added explicit modules to RapydScript. They work very similar to implicit modules of Python, with the exception that developer would use the module keyword rather than creating a new file. As a result, one can place multiple modules into a single file, or even nest them (note that import logic cannot import from within nested modules individually, only from files). In terms of JavaScript, a module is basically a function such that all of its local variables are visible to the outside. For example:

module math:
    pi = Math.PI

    a = "foo"
    a += "bar"

    counter = 0
    def inc_counter():
        nonlocal counter
        counter += 1
    def get_counter():
        return counter

    class Counter:
        def __init__(self):
            self.counter = 0
        def inc(self):
            self.counter += 1

print(math.pi)              # outputs 3.141592653589793
print(math.a)               # outputs 'foobar'

math.inc_counter()          # increments counter
print(math.counter)         # outputs 0 (math.counter has been imported by value, it will not update when internal counter does)
print(math.get_counter())   # outputs 1

c = math.Counter()          # creates a new instance of math.Counter, note the absence of 'new' keyword
c.inc()                     # increments counter
print(c.counter)            # outputs 1

Above examples show a few interesting features of RapydScript modules. First, note that modules allow all of the same logic inside of them as regular functions, as seen by appending 'bar' to 'foo'. Since module runs at the time of its initialization, any such logic will have already been executed by the time you reference the module from other code. This is how Python works with modules as well. Second, since JavaScript (like Python) passes primitive types by value, referencing them directly from outside the module will return their values at the time of module initialization rather than their current values. The example printing math.counter makes this clear. Third, RapydScript is smart enough to detect classes through modules, so you don't need to remember the new keyword for classes declared within modules.

The automatic new keyword is a useful feature, but what about modules that are declared in different files? If you import them in your code directly, RapydScript will pick them up automatically as well. If you instead include multiple .js files in your HTML, you could bypass the problem via decorators. Like classes (and functions), modules support @external decorator:

@external
module math:
    class Counter:
        pass

c = math.Counter()

The above example will treat math.Counter as a class, but will not create the class or the math module containing it, assuming that math.Class will be included from somewhere else when the page loads. Since modules are technically functions, they also support normal decorators, like the functions themselves:

def has_item(m):
    m.item = "an item"
    return m

@has_item
module blank:
    pass

print(blank.item)       # outputs 'an item'

This is unlike Python modules, but can be useful in case you want to tweak your modules further, like adding extra parameters to them or conditional debug logic. You could also nest modules as many levels as you wish:

module world:
    module country:
        co = 'US'
        module city:
            ct = 'Cupertino, CA'
            module zipcode:
                z = '95014'
                class House:
                    def __init__(self, address):
                        self.addr = address
                    def get_address(self):
                        return [self.addr, ct, z, co].join(', ')

apple_hq = world.country.city.zipcode.House('1 Infinite Loop')
apple_hq.get_address()      # '1 Infinite Loop, Cupertino, CA, 95014, US'

This is admittedly a silly example, since we hardcoded the city and zipcode yet dynamically specify the address, but it's a good example of what can be achieved via module-nesting.

Exception Handling

Like Python and JavaScript, RapydScript has exception handling logic. The following, for example, will warn the user if variable foo is not defined:

try:
    print(foo)
except:
    print("Foo wasn't declared yet")

It's a good practice, however, to only catch exceptions we expect. Imagine, for example, if foo was defined, but as a circular structure (with one of its attributes referencing itself):

foo = {}
foo.bar = foo

We would still trigger an exception, but for a completely different reason. A better way to rewrite our try/except block would be:

try:
    print(foo)
except ReferenceError:
    print("Foo wasn't declared yet")

We could also handle circular structure exception, if we needed to:

try:
    print(foo)
except ReferenceError:
    print("Foo wasn't declared yet")
except TypeError:
    print("One of foo's attributes references foo")

Or we could just dump the error back to the user:

try:
    print(foo)
except as err:
    print(err.name + ':' + err.message)

In this example, err is a JavaScript error object, it has name and message attributes, more information can be found at https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error. You can inherit from this object as if it was a class to create custom errors, just like you would in Python:

class MyError(Error):
    def __init__(self, message):
        self.name = Error
        self.message = message

raise MyError('This is a custom error!')

You can lump multiple errors in the same except block as well:

try:
    print(foo)
except ReferenceError, TypeError as e:
    print(e.name + ':' + e.message)

Basically, try/except/finally in RapydScript works very similar to the way it does in Python 3, lacking only the else directive (it didn't seem useful enough to implement). Like in Python and JavaScript, you can nest multiple exceptions inside each other, and use raise to throw the error back at the user:

try:
    print(foo)
except ReferenceError as e:
    if e.message == 'foo is not defined':
        print('undefined')
    else:
        raise e
finally:
    # reset foo
    foo = 'bar'

Like in Python (but unlike in regular JavaScript), you can use raise keyword by itself from within except block to reraise last-caught error.

Scope Control

Scope refers to the context of a variable. For example, a variable declared inside a function is not seen by the code outside the function. This variable's scope is local to the function. JavaScript controls scope via var keyword. Any variable that you start using without declaring with a var first will try to reference inner-most variable with the same name, if one doesn't exist, it will create a global variable with that name. For example, the following JavaScript code will not only return a incremented by 1, but also overwrite the global variable a with a+1:

a = 1;
a_plus_1 = function() {
    return ++a;
};

Basically, JavaScript defaults to outer or global scope if you omit var. This behavior can introduce some very frustrating bugs in large applications. To avoid this problem, RapydScript's scope preference works in reverse (same as Python's). RapydScript will prefer local-most scope, always creating a local variable if you perform any sort of assignment on it in a function (this is called variable shadowing). Shadowing can create another annoyance, however, of function's variable changes getting discarded. For example, at first, it looks like the following code will set a to 2:

a = 1
b = 1
increment = def():
    a += b
increment()

When executed, however, increment() function will discard any changes to a. This is because, like Python, RapydScript will not allow you to edit variables declared in outer scope. As soon as you use any sort of assignment with a in the inner scope, RapydScript will declare it as an internal variable, shadowing a in the outer scope. One way around this is to use the global keyword, declaring a as a global variable. This, however, must be done in every function that edits a. It also litters global scope, which it frowned upon because it can accidently overwrite an unrelated variable with the same name (declared by someone else or another library). RapydScript solves this by introducing nonlocal keyword (just like Python 3):

a = 1
b = 1
increment = def():
    nonlocal a
    a += b
increment()

Note that b is not affected by shadowing. It's the assignment operator that triggers shadowing, you can read outer-scope variables without having to use nonlocal. You can combine multiple non-local arguments by separating them with a comma: nonlocal a, b, c. You can also chain nonlocal declarations to escape multiple scopes:

def fun1():
    a = 5
    b = fun2():
        nonlocal a
        a *= 2
        c = fun3():
            nonlocal a
            a += 1

Shadowing is preferred in most cases, since it can't accidently damage outside logic, and if you want to edit an external variable, you're usually better off assigning function's return value to it. There are cases, however, when using nonlocal makes the code cleaner. There is also global, but it is rarely a good solution, and use of nonlocal is preferred to it.

Importing

Like Python, RapydScript allows you to import additional modules into your code. Unlike Python, however, (and like RapydML) the current implementation of the importing logic is naive. This means that RaoydScript doesn't separate different modules into separate namespaces, nor does it support module aliasing yet (eventually I do want that functionality).

For those unfamiliar with importing, let's imagine we're writing a very large program. This program is several thousand lines of code. We could dump it all into the same file, but that wouldn't be too clean (especially when we have multiple developers working on it). Alternatively, we could separate different chunks of the program into different files. Let's imagine, for example, that we're writing a videogame. We've already written a module that implements 'BasicCharacter' class, used by NPCs, monsters, and the main character. We've saved this class to Basic.pyj (that's the extension RapydScript prefers). Now let's create the main character in a different module:

import Basic

class MainCharacter(BasicCharacter):
    """
    This is the main character class, similar to basic but it implements attack and hp
    """
    def __init__(self):
        BasicCharacter.__init__(self)
        self.hp = 100
        self.damage = 10

    def attack(self, something):
        something.getHurt(self.damage)

    def getHurt(self, damage):
        self.hp -= damage

With RapydScript's importing, you don't need to worry about including each JavaScript file individually in your html. All your .pyj files will get concatenated into a single .js file that you can then include in your page. The name of the .js file will match the name of the file you used input for the RapydScript compiler.

RapydScript already adds several modules you can import by default, like stdlib and yql. Check out the examples or the documentation in the modules themselves for usage.

Available Libraries

One of Python's main strengths is the number of libraries available to the developer. This is something very few other Python-in-a-browser frameworks understand. In the browser JavaScript is king, and no matter how many libraries the community for the given project will write, the readily-available JavaScript libraries will always outnumber them. This is why RapydScript was designed with JavaScript and DOM integration in mind from the beginning. Indeed, plugging underscore.js in place of RapydScript's stdlib will work just as well, and some developers may choose to do so, after all, underscore.js is very Pythonic and very complete. Likewise, sprintf.js (https://npmjs.org/package/sprintf-js) can be used with RapydScript to replicate Python's string interpolation.

It is for that reason that I try to keep RapydScript bells and whistles to a minimum. RapydScript's main strength is easy integration with JavaScript and DOM, which allows me to stay sane and not rewrite my own versions of the libraries that are already available. That doesn't mean, however, that pythonic libraries can't be written for RapydScript. To prove that, I have implemented lightweight clones of several popular Python libraries and bundled them into RapydScript, you can find them in src directory. The following libraries are included:

stdlib/stdlib2      # see stdlib section
math                # replicates almost all of the functionality from Python's math library
re                  # replicates almost all of the functionality from Python's re library
unittest            # replicates almost all of the functionality from Python's unittest library
random              # replicates most of the functionality from Python's random library
yql                 # lightweight library for performing Yahoo Query Language requests

For the most part, the logic implemented in these libraries functions identically to the Python versions. One notable exception is that unittest library requires that classes be bound to the global (nodejs) or window (browser) object to be picked up by unittest.main(). An example in unitetest.pyj shows this usage. I'd be happy to include more libraries, if other members of the community want to implement them (it's fun to do, re.pyj is a good example), but I want to reemphasize that unlike most other Python-to-JavaScript compilers, RapydScript doesn't need them to be complete since there are already tons of available JavaScript libraries that it can use natively.

Advanced Usage Topics

This section contains various topics which might be of interest to the programmer writing large projects using RapydScript, but might not be relevant to a programmer who is just getting started with RapydScript. The topics in this section focus on coding conventions to keep your code clean, optimizations, and additional libraries that come with RapydScript, as well as suggestions for writing your own libraries.

Browser Compatibility

By default, RapydScript compiles your logic such that it will work on modern browsers running HTML5, as well as older browser like IE6-8. To do so, the compiler sometimes has to generate rather messy and inefficient output. As of September, 2013, less than 8% of the world has been found to be using IE8. If you know that your users will not be using older browsers, you can compile your logic with --screw-ie8 option to generate cleaner, faster code.

Code Conventions

It's not hard to see that RapydScript is a cleaner language than JavaScript. However, like with all dynamically-typed languages (including Python), it's still easy to shoot yourself in the foot if you don't follow some sort of code conventions. Needless to say, they're called conventions for a reason, feel free to ignore them if you already have a set of conventions you follow or if you disagree with some.

Tabs vs Spaces

This seems to be a very old debate. Python code conventions suggest 4-space indent, most of the bundled RapydScript files use 1-tab for indentation. The old version of RapydScript relied on tabs, new one uses spaces since that seems to be more consistent in both Python and JavaScript communities. Use whichever one you prefer, as long as you stay consistent. If you intend to submit your code to RapydScript, it must use spaces to be consistent with the rest of the code int he repository.

Object Literals vs Hashes/Dicts

JavaScript treats object literals and hashes as the same thing. I'm not a fan of this policy. Some of the problems you can see resulting from this is Google Closure compiler's ADVANACED_OPTIMIZATIONS breaking a lot of seemingly-good JavaScript code. The main problem for most of the code that breaks seems to be renaming of methods/variables in one place and not another. My suggestion is to ALWAYS treat object literals as object literals and ALWAYS treat hashes as hashes, basically be consistent about quoting your keys. As an added bonus, your code will have a much better chance of compiling correctly via Closure compiler. For example:

obj = {
    foo:    1,
    bar:    def(): print('bar' + str(foo))
}
hash = {
    'foo':  1,
    'bar':  def(): print('bar' + str(foo))
}

obj.bar()       # good
obj['bar']()    # bad

hash.bar()      # bad
hash['bar']()   # good

Semi-Colons

Don't abuse semi-colons. They're meant as a way to group related logic together, not to fit your entire web-app on one line. The following is fine:

X = 0; Y = 1

Anything that requires more than a couple semi-colons, however, or involves long mathematical computations, is better off on its own line. Use your discretion, if the logic requires more than one visual pass-through from the programmer to understand the flow, you probably shouldn't use semi-colons. A Fibanacci function, as shown below, would probably be the upper limit of the kind of logic you could sanely represent with semi-colons:

fib = def(x): if x<=1: return 1; return fib(x-1)+fib(x-2)

Even for this example, however, I'd personally prefer to use multiple lines.

jQuery-wrapped Elements

If you use jQuery with your app, you will probably be storing these into variables a lot. If you've written a decently sized app, you've probably mistaken a bare element with wrapped element at least once. This is especially true of objects like canvas, where you need to access object's attributes and methods directly. My solution for these is simple, prepend jQuery-wrapped elements with $:

$canvas = ('<canvas></canvas>')
canvas = $canvas.get(0)
ctx = canvas.getContext('2d')
$canvas.appendTo(document)

This is especially useful with function definitions, since you will immediately know what kind of object the function takes in just by skimming its signature.

Libraries

I recommend that developers rely on native JavaScript logic when possible, rather than libraries like math.pyj and re.pyj. While they mimic Python without problems and work quite well, they introduce extra overhead that your web app doesn't need. Additionally, I think re module in Python is unnecessarily complex, and JavaScript's RegExp object is much easier to use. With that said, these libraries can be extremely useful for porting large applications from Python to the web, but if you're writing new code, it will probably be easier to maintain if you decide to use native JavaScript alternatives (such as Math and RegExp) instead.

Quirks

In a perfect world, software works flawlessly and doesn't have any special cases requiring workarounds on the user's part. In a less-perfect world, all quirks are due to the software itself and can be fixed. In our world, we not only have to deal with the quirks of the software we're using, but other software it interacts with. RapydScript is no exception, in addition to its own quirks there are a few quirks brought upon us by the browser as well as a few bugs in jQuery that affect us. Here is a list of things you need to be aware of:

  • RapydScript automatically appends 'new' keyword when using classes generated by it, it does not append it to objects created by another library, but it does append it to native JavaScript objects like Image and RegExp.
  • jQuery erroneously assumes that no other library will be modifying JavaScript's 'Object', and fails to do object.hasOwnProperty() check in multiple places where it should. To avoid breaking it, I had to implement stdlib such that dictionary methods are methods of a different object. Regular Python allows you to call hash.keys() as well as dict.keys(hash). RapydScript only supports the second notation - which is admittedly a bit more awkward.

Rapydscript Examples

Asteroids Example

Copyright 2013 Pyjeon Software LLC
Author: Charles Law (based on Pyjamas Asteroids)
License: Creative Commons: Attribution + Noncommerial + ShareAlike

Preview

RapydScript Asteroids

The asteroids game was originally written as a Pyjs (formally Pyjamas) example. With some relatively minor changes, the code was ported over to RapydScript. The RapydScript verion has the full functionality of the game but with a significantly smaller footprint. The important metrics from our perspective are:

Download Size: 18 kb   vs 1200 kb
Download Time: 1.3 sec vs  17 sec
LOC:             700   vs   32900

It is also worth noting that there are currently 6 cache files generated for Pyjs so the app takes significantly more storage space.

Example in action: RapydScript Asteroids

Pyjs Asteroids

The Pyjs Asteroids game builds to approximately 1200kb for a single cache file. End users will have to wait significantly longer to load a webpage.

For developers, Pyjs can make applications appear slow and use more bandwidth to transfer. It is also worth noting that Pyjs generates 6 cache files that all must be deployed.

Example in Action: Pyjs Example

asteroids.pyj

"""
View

The view contains the canvas and has all the logic for drawing the game state
on the screen. The original Pyjs code used multiple libraries here. Of the 3
files, this one required the most changes to port to RapydScript.
"""

import stdlib
import game_model
import game_controller

SHOT_COLOR = '#fff'

ASTEROID_SIZE = 180.0
CANVAS_DIM_X = 800
CANVAS_DIM_Y = 600

class View:
    def __init__(self, w, h, canvas):
        self.width = w
        self.height = h
        self.canvas = canvas

        self.model = Model(w, h)
        self.controller = Controller(self.model)

        self.number_images_loaded = 0
        self.view_loaded = False

        document.addEventListener('keydown', def (event):
            event.preventDefault()
            self.setKey(event.keyCode, True)
        )
        document.addEventListener('keyup', def (event):
            event.preventDefault()
            self.setKey(event.keyCode, False)
        )

        #Load images
        imgsrcs = ['./images/ship1.png',
                    './images/ship2.png',
                    './images/asteroid.png']

        self.img = [None, None, None]
        for i in range(3):
            self.img[i] = Image()
            self.img[i].src = imgsrcs[i]
            self.img[i].onload = def():
                self.number_images_loaded += 1
                if self.number_images_loaded >= 3:
                    self.view_loaded = True
                    self.controller.start_game(self)

    def setKey(self, k, set):
        if k == 38:
            self.controller.key_up = set
        elif k == 40:
            self.controller.key_down = set
        elif k == 37:
            self.controller.key_left = set
        elif k == 39:
            self.controller.key_right = set
        elif k == 32:
            self.controller.key_fire = set

    def draw_asteroid(self, ctx, asteroid):
        ctx.save()
        ctx.translate(asteroid.x, asteroid.y)
        ctx.rotate(asteroid.rot)
        ctx.scale(asteroid.scale,asteroid.scale)
        ctx.drawImage(self.img[2], -(ASTEROID_SIZE/2), -(ASTEROID_SIZE/2))
        ctx.restore()

    def draw_shot(self, ctx, shot):
        ctx.fillStyle = SHOT_COLOR
        ctx.fillRect(Math.ceil(shot.x - 1), Math.ceil(shot.y - 1), 3, 3)

    def draw_ship(self, ctx, ship):
        ctx.save()
        ctx.translate(ship.x, ship.y)
        ctx.rotate(ship.rot)
        if self.controller.key_up:
            img = self.img[1]
        else:
            img = self.img[0]
        ctx.drawImage(img, -15, -12)
        ctx.restore()

    def draw(self):
        ctx = self.canvas.getContext('2d')

        #fill the background
        ctx.fillStyle = '#000'
        ctx.fillRect(0, 0, self.width, self.height)

        for i in range(len(self.model.asteroids)):
            self.draw_asteroid(ctx, self.model.asteroids[i])
        for i in range(len(self.model.shots)):
            self.draw_shot(ctx, self.model.shots[i])

        self.draw_ship(ctx, self.model.ship)

window.runGame = def():
    # The view tells when the controller to start and passes in a reference
    # to itself
    canvas = document.getElementById('myCanvas')
    view = View(CANVAS_DIM_X, CANVAS_DIM_Y, canvas)

game_controller.pyj

#   Copyright 2012 Charles Law
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

"""
Controller

The controller runs the game and modifies the Model. This required very few changes
to port from Pyjs.
"""

import game_model

class Controller:
    def __init__(self, model):
        self.model = model
        self.key_up = False
        self.key_down = False
        self.key_left = False
        self.key_right = False
        self.key_fire = False

    def start_game(self, view):
        self.view = view
        self.model.start_game(view)
        self.model.reset()

        #setup a timer
        setInterval(def():
            self.update()
        , 1000/FPS)

    def update(self):
        self.keyboard_updates()
        self.model.update()

    def keyboard_updates(self):
        ship = self.model.ship

        drot = 0
        if self.key_left:
            drot -= ROTATE_SPEED
        if self.key_right:
            drot += ROTATE_SPEED
        if drot:
            ship.rotate_ship(drot)

        if self.key_up:
            ship.thrust()
        else:
            ship.friction()

        if self.key_fire:
            self.model.trigger_fire()

game_model.pyj

#   Copyright 2009 Joe Rumsey
#   Copyright 2012 Charles Law
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

"""
Model

The model contains the classes representing the various game objects - the asteroid, ship
and shots. These are used to store all the state information on these different objects
as well as helping modify them. The original Pyjs code did not use any libraries so
porting over this code required almost no changes to the code.
"""

NUM_ASTEROIDS = 2
FPS = 30
FRICTION = 0.05
THRUST = 0.2
ROTATE_SPEED_PER_SEC = Math.PI
ROTATE_SPEED = ROTATE_SPEED_PER_SEC / FPS
MAX_ASTEROID_SPEED = 2.0
SHOT_LIFESPAN = 60
SHOT_SPEED = 8.0
SHOT_DELAY = 10
ASTEROID_SIZE = 180.0
ASTEROID_SIZES = [90.0, 45.0, 22.0, 11.0]

def randfloat(min, max):
    return Math.random() * (max - min) + min

def randint(min, max):
    return Math.floor(Math.random() * (max - min + 1)) + min

def distsq(x1,y1, x2,y2):
    return ((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2))

class Asteroid:
    def __init__(self, model, x=None, y=None, size=0):
        self.model = model
        if x is None or y is None:
            self.x = model.x/2
            self.y = model.y/2
            while distsq(self.x, self.y, model.x / 2, model.y / 2) < (180*180):
                self.x = randint(0, model.x)
                self.y = randint(0, model.y)
        else:
            self.x = x
            self.y = y

        self.dx = randfloat(0, MAX_ASTEROID_SPEED)
        self.dy = randfloat(0, MAX_ASTEROID_SPEED)
        self.rot = randfloat(0, 2*Math.PI) - Math.PI
        self.rotspeed = randfloat(0, 0.1) - 0.05
        self.size = size
        self.radius = ASTEROID_SIZES[self.size]
        self.radius2 = self.radius*self.radius
        self.scale = (self.radius / ASTEROID_SIZE) * 2

    def update_dim(self, pos, d_dim, max_dim):
        pos += d_dim

        if d_dim < 0:
            if pos <= 0:
                pos = -pos
                d_dim = -d_dim
        else:
            if pos >= max_dim:
                d_dim = -d_dim
                pos = 2*max_dim - pos
        return pos, d_dim

    def move(self):
        self.x, self.dx = self.update_dim(self.x, self.dx, self.model.x)
        self.y, self.dy = self.update_dim(self.y, self.dy, self.model.y)

        self.rot += self.rotspeed

class Shot:
    def __init__(self, model, ship):
        self.model = model
        self.x = ship.x
        self.y = ship.y
        self.dx = ship.dx
        self.dy = ship.dy
        self.direction = ship.rot
        self.lifespan = SHOT_LIFESPAN

    def move(self):
        self.lifespan -= 1
        if self.lifespan <= 0:
            return False
        self.x = self.x + self.dx + SHOT_SPEED * Math.sin(self.direction)
        self.y = self.y + self.dy - SHOT_SPEED * Math.cos(self.direction)
        for i, a in enumerate(self.model.asteroids):
            if distsq(self.x, self.y, a.x, a.y) < (a.radius2):
                self.model.split_asteroid(i)
                return False

        return True

class Ship:
    def __init__(self, cx, cy):
        self.cx = cx
        self.cy = cy
        self.reset()

    def rotate_ship(self, drot):
        self.rot += drot

        if drot < 0-Math.PI:
            drot += 2*Math.PI
        elif drot > Math.PI:
            drot -= 2*Math.PI

    def thrust(self):
        self.dx += THRUST * Math.sin(self.rot)
        self.dy -= THRUST * Math.cos(self.rot)

    def friction(self):
        if Math.abs(self.dx) < 0.001 and Math.abs(self.dy) < 0.001:
            self.dx = 0
            self.dy = 0
        else:
            direction = Math.atan2(self.dx, self.dy)
            self.dx -= FRICTION * Math.sin(direction)
            self.dy -= FRICTION * Math.cos(direction)

    def move(self):
        self.shot_delay -= 1

        self.x += self.dx
        self.y += self.dy
        if self.dx > 0 and self.x >= self.cx:
            self.x -= self.cx
        elif self.dx < 0 and self.x < 0:
            self.x += self.cx
        if self.y > 0 and self.y >= self.cy:
            self.y -= self.cy
        elif self.dy < 0 and self.y < 0:
            self.y += self.cy

    def reset(self):
        self.x = self.cx/2
        self.y = self.cy/2
        self.dx = 0
        self.dy = 0
        self.rot = 0
        self.shot_delay = 0

class Model:
    def __init__(self, x, y):
        self.x = x
        self.y = y

        self.num_asteroids = NUM_ASTEROIDS
        self.ship = Ship(x, y)

    def start_game(self, view):
        self.view = view

    def update(self):
        # Make model updates
        for a in self.asteroids:
            a.move()
            if (distsq(self.ship.x, self.ship.y, a.x, a.y) < (a.radius2)):
                self.destroyShip()
                return

        for i in reversed(range(len(self.shots))):
            if not self.shots[i].move():
                self.shots.pop(i)

                if len(self.asteroids) == 0:
                    self.start_next_level()
                    return

        self.ship.move()

        self.view.draw()

    def start_next_level(self):
        self.num_asteroids += 1
        self.reset()

    def destroyShip(self):
        self.num_asteroids = NUM_ASTEROIDS
        self.reset()

    def reset(self):
        self.shots = []
        self.asteroids = [Asteroid(self) for i in range(self.num_asteroids)]
        self.ship.reset()

    def trigger_fire(self):
        if self.ship.shot_delay > 0:
            return
        else:
            self.shots.append(Shot(self, self.ship))
            self.ship.shot_delay = SHOT_DELAY

    def split_asteroid(self, i):
        a = self.asteroids[i]
        if a.size < len(ASTEROID_SIZES) - 1:
            for j in range(2):
                self.asteroids.append(Asteroid(self, a.x, a.y, a.size + 1))
        self.asteroids.pop(i)

D3 TreeMap Example

Copyright 2013 Pyjeon Software LLC
Author: Charles Law License: Creative Commons: Attribution + Noncommerial + ShareAlike

TreeMap

This is an example using the D3.js (Data-Driven Documents) library taken from http://bl.ocks.org/mbostock/4063582. This example was a test case of the RapydScript decompiler.

treemap.pyj

margin = {
    top: 40,
    right: 10,
    bottom: 10,
    left: 10
}
width = 960 - margin.left - margin.right
height = 500 - margin.top - margin.bottom

color = d3.scale.category20c()

treemap = d3.layout.treemap().size([ width, height ]).sticky(True)\
        .value(def(d):
    return d.size
)

div = d3.select("body").append("div").style("position", "relative").\
    style("width", width + margin.left + margin.right + "px").\
    style("height", height + margin.top + margin.bottom + "px").\
    style("left", margin.left + "px").style("top", margin.top + "px")

d3.json("flare.json", def(error, root):
    node = div.datum(root).selectAll(".node").data(treemap.nodes).\
        enter().append("div").attr("class", "node").call(position).\
        style("background", def(d):
        #Note: This is actually indented 1 level it's just not obvious
        if (d.children):
            return color(d.name)
        else:
            return None
    ).text(def(d):
        if (d.children):
            return None
        else:
            return d.name
    )
    d3.selectAll("input").on("change", def change():
        if (this.value is "count"):
            value = def():
                return 1
        else:
            value = def(d):
                return d.size

        node.data(treemap.value(value).nodes).transition().duration(1500).\
            call(position)
    )
)

def position():
    this.style("left", def(d): return d.x + "px"
    ).style("top", def(d): return d.y + "px"
    ).style("width", def(d): return Math.max(0, d.dx - 1) + "px"
    ).style("height", def(d): return Math.max(0, d.dy - 1) + "px"
    )

Paint App Example

Copyright 2012 Pyjeon Software LLC
Author: Alexander Tsepkov
License: License: Creative Commons: Attribution + Noncommerial + ShareAlike

Preview

This paint app is meant to showcase RapydScript simplicity and power. It employs multiple features of RapydScript as well as RapydML and RapydCSS, it shows DRY (don't repeat yourself) coding style when writing RapydScript as well as building the webpage itself in RapydML. It uses multiple HTML5 features to cleanly separate CSS, HTML, and JavaScript. This app also showcases RapydScript's ability to integrate with other widgets and frameworks (jQuery, jQuery UI, and Farbtastic widget). It leverages many of JavaScript's design patterns (closures/function generators) while still keeping the code clean, as one would expect in Python. It might be hard to tell from this example alone, but it is also extremely efficient. The paint-bucket algorithm, for example, when implemented in Pyjamas, takes over a second to complete (and only works in Firefox), but is instantaneous here and works in all browsers.

Drawing.pyj

######################################################
# Copyright 2012 Pyjeon Software LLC
# Author:   Alexander Tsepkov
# License:  License: Creative Commons: Attribution + Noncommerial + ShareAlike
######################################################

# modes we will be using for drawing
BRUSH   = 0
ERASER  = 1
LINE    = 2
SELECT  = 3
COLORSELECT = 4
LASSO   = 5
RECT    = 6
ELLIPSE = 7
SAMPLER = 8
BUCKET  = 9
TEXT    = 10

# misc
X       = 0
Y       = 1
CLICK   = 1
RCLICK  = 3

class Drawing:
    """
    Controls the currently displayed image, and handles undo/redo logic
    """
    def __init__(self):

        # these are meant to be private
        self._canvas = $('#perm-dwg').get(0)        # get actual DOM element
        self._ctx = ctx = self._canvas.getContext('2d')
        self._undoStack = []
        self._redoStack = []
        self._fillColor = None
        self._strokeColor = None

        # and these are not exposed at all
        $tmpCanvas = $('#temp-dwg')
        tmpCanvas = $tmpCanvas.get(0)
        tmpCtx = tmpCanvas.getContext('2d')
        canvasWidth = tmpCanvas.width
        canvasHeight = tmpCanvas.height

        dragging = False
        points = []
        selection = None
        lastPt = [0, 0]
        transparent_bg = True

        #################
        # utility functions
        #################
        getXY = def(obj, event):
            # +0.5 allows us to snap to the center of the pixel, preventing interpolation and
            # making our images more crisp
            absolute = $(obj).offset()
            return event.pageX - absolute.left, event.pageY - absolute.top
        normalize = def(x, y, width, height):
            if width < 0:
                width = -width
                x = x-width
            if height < 0:
                height = -height
                y = y-height
            return x, y, width, height
        ellipse = def(context, x, y, width, height):
            x, y, width, height = normalize(x, y, width, height)
            ctrX = (x+x+width)/2
            ctrY = (y+y+height)/2
            circ = Math.max(width, height)
            scaleX = width/circ
            scaleY = height/circ
            context.save()
            context.translate(ctrX, ctrY)
            context.scale(scaleX, scaleY)
            context.arc(0, 0, circ/2, 0, 2*Math.PI)
            context.restore()
        drawSpline = def(context, lastPoint):
            if lastPoint is undefined:
                lastPoint = points[len(points)-1]
            context.beginPath()
            context.moveTo(points[0][X], points[0][Y])
            if len(points) == 1:
                context.lineTo(lastPoint[X], lastPoint[Y])
            elif len(points) == 2:
                context.quadraticCurveTo(points[1][X], points[1][Y], lastPoint[X], lastPoint[Y])
            else:
                context.bezierCurveTo(\
                    points[1][X], points[1][Y], \
                    points[2][X], points[2][Y], \
                    lastPoint[X], lastPoint[Y])
            context.stroke()
        sample = def(x, y, click):
            data = ctx.getImageData(x, y, 1, 1).data
            if data[3]:
                color = 'rgb('+data[0]+','+data[1]+','+data[2]+')'
            else:
                color = 'transparent'
            if click == CLICK:
                tag = '#stroke'
            else:
                tag = '#fill'

            if color == 'transparent':
                color = None
                $(tag).css('background', '')
            else:
                $(tag).css('background', color)
            if click == CLICK:
                self._brushColor = color
            else:
                self._fillColor = color
        matchStartColor = def(colorLayer, pixelPos, startPixel):
            r = colorLayer[pixelPos]
            g = colorLayer[pixelPos+1]
            b = colorLayer[pixelPos+2]
            a = colorLayer[pixelPos+3]
            return r == startPixel[0] and g == startPixel[1] and \
                    b == startPixel[2] and bool(a) == bool(startPixel[3])
        eachPixel = def(imageData, callback):
            offset = 0
            length = imageData.height * imageData.width * 4
            while offset < length:
                callback(offset)
                offset += 4
        self._clear = clear = def(context):
            context.clearRect(0, 0, canvasWidth, canvasHeight)

        #################
        # user input
        #################
        onMouseDown = def(event):
            nonlocal dragging, points, lastPt, selection

            event.preventDefault()  # prevents change to mouse cursor
            dragging = True
            x, y = getXY(this, event)
            self._undoStack.append(ctx.getImageData(0, 0, canvasWidth, canvasHeight))
            self._redoStack = []
            ctx.save()
            tmpCtx.save()
            if self._mode in [BRUSH, ERASER]:
                ctx.lineWidth = self._lineWidth
                if self._mode == BRUSH:
                    ctx.strokeStyle = self._brushColor
                else:
                    ctx.globalCompositeOperation = 'destination-out'
                ctx.beginPath()
                ctx.moveTo(x, y)
            elif self._mode in [RECT, ELLIPSE]:
                tmpCtx.lineWidth = self._lineWidth
                tmpCtx.strokeStyle = self._brushColor
                tmpCtx.fillStyle = self._fillColor
                points.append([x, y])
            elif self._mode == LINE:
                tmpCtx.lineWidth = self._lineWidth
                tmpCtx.strokeStyle = self._brushColor
                points.append([x, y])
            elif self._mode == SAMPLER:
                sample(x, y, event.which)
            elif self._mode == BUCKET and self._fillColor:
                # paint-bucket algorithm based on one from this website:
                # http://www.williammalone.com/articles/html5-canvas-javascript-paint-bucket-tool/
                pixelStack = [(x, y)]
                colorLayer = ctx.getImageData(0, 0, canvasWidth, canvasHeight)
                data = colorLayer.data
                startPixel = ctx.getImageData(x, y, 1, 1).data

                # convert swatch color to imageData (we're using canvas to avoid dealing with literal
                # color names like 'aquamarine')
                tmpCtx.save()
                tmpCtx.fillStyle = self._fillColor
                locX, locY = 50, 50 # pick point far enough from the corner so it's not affected by border style
                tmpCtx.fillRect(locX, locY, 1, 1)
                swatchPixel = tmpCtx.getImageData(locX, locY, 1, 1).data
                tmpCtx.clearRect(locX, locY, 1, 1)
                tmpCtx.restore()
                if not matchStartColor(swatchPixel, 0, startPixel):
                    while len(pixelStack):
                        newPos = pixelStack.pop()
                        x = newPos[X]
                        y = newPos[Y]
                        pixelPos = (y*canvasWidth + x)*4
                        while y >= 0 and matchStartColor(data, pixelPos, startPixel):
                            y -= 1
                            pixelPos -= canvasWidth*4
                        pixelPos += canvasWidth*4
                        y += 1
                        reachLeft = False
                        reachRight = False
                        while y < canvasHeight-1 and matchStartColor(data, pixelPos, startPixel):
                            y += 1
                            data[pixelPos] = swatchPixel[0]
                            data[pixelPos+1] = swatchPixel[1]
                            data[pixelPos+2] = swatchPixel[2]
                            data[pixelPos+3] = swatchPixel[3]
                            if x > 0:
                                if matchStartColor(data, pixelPos-4, startPixel):
                                    if not reachLeft:
                                        pixelStack.append((x-1, y))
                                        reachLeft = True
                                elif reachLeft:
                                    reachLeft = False
                            if x < canvasWidth-1:
                                if matchStartColor(data, pixelPos+4, startPixel):
                                    if not reachRight:
                                        pixelStack.append((x+1, y))
                                elif reachRight:
                                    reachRight = False
                            pixelPos += canvasWidth*4
                    ctx.putImageData(colorLayer, 0, 0)
            elif self._mode == SELECT:
                lastPt = [x, y]
                if selection is None:
                    tmpCtx.lineWidth = 1
                    tmpCtx.strokeStyle = 'rgb(0,255,0)'
                    points.append([x, y])
                elif not (points[0][X] < x and x < points[1][X] \
                and points[0][Y] < y and y < points[1][Y]):
                    # we just deselected our selection, dump it back to main canvas
                    if transparent_bg:
                        # background is transparent, merge two images together
                        tmp = ctx.getImageData(points[0][X], points[0][Y], \
                            points[1][X]-points[0][X], points[1][Y]-points[0][Y]).data
                        data = selection.data
                        merge = def(offset):
                            if not data[offset+3]:
                                data[offset] = tmp[offset]
                                data[offset+1] = tmp[offset+1]
                                data[offset+2] = tmp[offset+2]
                                data[offset+3] = tmp[offset+3]
                        eachPixel(selection, merge)
                    ctx.putImageData(selection, points[0][X], points[0][Y])
                    clear(tmpCtx)
                    selection = None
                    points = [[x, y]]

        onMouseMove = def(event):
            nonlocal lastPt

            if self._mode == LINE and len(points):
                x, y = getXY(this, event)
                clear(tmpCtx)
                drawSpline(tmpCtx, (x, y))
            elif dragging:
                x, y = getXY(this, event)
                if self._mode in [BRUSH, ERASER]:
                    ctx.lineTo(x, y)
                    ctx.stroke()
                elif self._mode in [RECT, ELLIPSE]:
                    clear(tmpCtx)
                    tmpCtx.beginPath()
                    if self._mode == RECT:
                        tmpCtx.rect(points[0][X], points[0][Y], x-points[0][X], y-points[0][Y])
                    else:
                        ellipse(tmpCtx, points[0][X], points[0][Y], x-points[0][X], y-points[0][Y])

                    if self._fillColor is not None:
                        tmpCtx.fill()
                    if self._brushColor is not None:
                        tmpCtx.stroke()
                elif self._mode == SAMPLER:
                    sample(x, y, event.which)
                elif self._mode == SELECT:
                    clear(tmpCtx)
                    if selection is not None:
                        for point in points:
                            point[0] += x-lastPt[X]
                            point[1] += y-lastPt[Y]
                        lastPt = [x, y]
                        x = points[1][X]
                        y = points[1][Y]
                        tmpCtx.putImageData(selection, points[0][X], points[0][Y])
                    tmpCtx.strokeRect(points[0][X], points[0][Y], x-points[0][X], y-points[0][Y])

        onMouseUp = def(event):
            nonlocal dragging, points, selection

            if self._mode in [RECT, ELLIPSE]:
                x, y = getXY(this, event)
                ctx.beginPath()
                ctx.lineWidth = self._lineWidth
                ctx.strokeStyle = self._brushColor
                ctx.fillStyle = self._fillColor
                if self._mode == RECT:
                    ctx.rect(points[0][X], points[0][Y], x-points[0][X], y-points[0][Y])
                else:
                    ellipse(ctx, points[0][X], points[0][Y], x-points[0][X], y-points[0][Y])

                if self._fillColor is not None:
                    ctx.fill()
                if self._brushColor is not None:
                    ctx.stroke()
            elif self._mode == LINE and event.which == CLICK and len(points) > 1:
                ctx.lineWidth = self._lineWidth
                ctx.strokeStyle = self._brushColor
                drawSpline(ctx)
            elif self._mode == SELECT:
                x, y = getXY(this, event)
                if selection is None and x != points[0][X] and y != points[0][Y]:
                    # we just selected something
                    sx = points[0][X]
                    sy = points[0][Y]
                    width = x-sx
                    height = y-sy
                    sx, sy, width, height = normalize(sx, sy, width, height)
                    points = [[sx, sy]]
                    x = sx+width
                    y = sy+height
                    selection = ctx.getImageData(sx, sy, width, height)
                    ctx.clearRect(sx, sy, width, height)
                    tmpCtx.putImageData(selection, points[0][X], points[0][Y])
                    tmpCtx.strokeRect(points[0][X], points[0][Y], x-points[0][X], y-points[0][Y])
                    points.append([x, y])

            dragging = False
            ctx.restore()
            if (not selection or (x == points[0][X] and y == points[0][Y])) \
            and (self._mode != LINE or (event.which == CLICK and len(points) > 1)):
                points = []
                clear(tmpCtx)
                tmpCtx.restore()

        # to be used by selection manipulation filters
        self._filter = def(callback):
            nonlocal selection
            if selection:
                pixels = selection
            else:
                pixels = ctx.getImageData(0, 0, canvasWidth, canvasHeight)
            data = pixels.data
            invoke = def(offset):
                callback(data, offset)
            eachPixel(pixels, invoke)
            if selection:
                clear(tmpCtx)
                tmpCtx.putImageData(pixels, points[0][X], points[0][Y])
                tmpCtx.strokeRect(points[0][X], points[0][Y], points[1][X]-points[0][X], points[1][Y]-points[0][Y])
            else:
                ctx.putImageData(pixels, 0, 0)

        $tmpCanvas.mousedown(onMouseDown)
        $tmpCanvas.mousemove(onMouseMove)
        $tmpCanvas.mouseup(onMouseUp)
        onMouseLeave = def(event):
            if dragging:
                onMouseUp(event)
        $tmpCanvas.mouseleave(onMouseLeave)

        self._ctx.lineJoin = tmpCtx.lineJoin = self._ctx.lineCap = tmpCtx.lineCap = 'round'

    def undo(self):
        state = self._undoStack.pop()
        if state:
            self._redoStack.append(state)
            self._ctx.putImageData(state, 0, 0)

    def redo(self):
        state = self._redoStack.pop()
        if state:
            self._undoStack.append(state)
            self._ctx.putImageData(state, 0, 0)

    # invert declared inside __init__() since it needs access to hidden 'selection' variable

    def setMode(self, mode):
        self._mode = mode

    def setStroke(self, style):
        self._brushColor = style

    def setFill(self, style):
        self._fillColor = style

    def setWidth(self, value):
        self._lineWidth = value

    def clear(self):
        self._clear(self._ctx)

    def exportDwg(self):
        return self._canvas.toDataURL()

    def invert(self):
        invert = def(data, offset):
            data[offset] = 255-data[offset]
            data[offset+1] = 255-data[offset+1]
            data[offset+2] = 255-data[offset+2]
        self._filter(invert)

    def redFilter(self):
        remove = def(data, offset):
            data[offset] = 0
        self._filter(remove)

    def greenFilter(self):
        remove = def(data, offset):
            data[offset+1] = 0
        self._filter(remove)

    def blueFilter(self):
        remove = def(data, offset):
            data[offset+2] = 0
        self._filter(remove)

    def darken(self):
        darken = def(data, offset):
            data[offset] /= 2
            data[offset+1] /= 2
            data[offset+2] /= 2
        self._filter(darken)

    def lighten(self):
        lighten = def(data, offset):
            data[offset] = Math.min(data[offset] * 2, 255)
            data[offset+1] = Math.min(data[offset+1] * 2, 255)
            data[offset+2] = Math.min(data[offset+2] * 2, 255)
        self._filter(lighten)

paint.pyj

######################################################
# Copyright 2012 Pyjeon Software LLC
# Author:   Alexander Tsepkov
# License:  License: Creative Commons: Attribution + Noncommerial + ShareAlike
######################################################

import Drawing

class ColorSwatch:
    """
    Color selection and preview widget
    """
    def __init__(self):
        fg = $('<div></div>')\
            .width(36).height(36)\
            .css({'position': 'absolute', 'border': '1px solid black'})
        fg.change = def(color):
            $(self).css('background', color)
        bg = fg.clone()
        $('#color-swatch')

def main():
    dwg = Drawing()

    # control brush sizes
    # NOTE: jQuery UI spinner implementation is inconsistent with its documentation, since their
    # implementation was buggy, this required some testing and work-arounds on my end, in particular
    # .value() method doesn't exist (.val() does), and .change() doesn't trigger when using the
    # arrows to update the value, instead I had to add my own manual trigger for it
    onChange = def():
        dwg.setWidth($(this).val())
    $brushWidget = $('#brush-size').spinner({'min': 1, 'max': 40})
    $brushWidget.change(onChange)
    triggerChange = def():
        $(this).siblings('input').change()
    $('.ui-spinner-button').click(triggerChange)
    $brushWidget.val(1)

    # map toolbox buttons to drawing modes
    $tools = $('.toolbox-item')
    makeMode = def(mode):
        setMode = def(event):
            dwg.setMode(mode)
            $tools.removeClass('selected')
            $(event.target).parent().addClass('selected')
        return setMode
    $('#toolbox-brush').click(makeMode(BRUSH))
    $('#toolbox-eraser').click(makeMode(ERASER))
    $('#toolbox-line').click(makeMode(LINE))
    $('#toolbox-select').click(makeMode(SELECT))
    $('#toolbox-colorselect').click(makeMode(COLORSELECT))
    $('#toolbox-lasso').click(makeMode(LASSO))
    $('#toolbox-rectangle').click(makeMode(RECT))
    $('#toolbox-ellipse').click(makeMode(ELLIPSE))
    $('#toolbox-sampler').click(makeMode(SAMPLER))
    $('#toolbox-bucket').click(makeMode(BUCKET))
    $('#toolbox-text').click(makeMode(TEXT))
    $('#toolbox-brush').click()     # default to brush

    # set up color selection menus
    $stroke = $('#stroke')
    $fill = $('#fill')
    setStroke = def(style):
        dwg.setStroke(style)
        $stroke.css('background', style)
    setFill = def(style):
        dwg.setFill(style)
        $fill.css('background', style)
    setStroke('black')

    # farbtastic is an open-source color-picker we will be leveraging to avoid reinventing the wheel:
    # http://acko.net/blog/farbtastic-jquery-color-picker-plug-in/
    $colorPickers = $('.colorpicker')
    for $swatch, idtag, resetid, callback in [($stroke, '#fg-color', '#no-stroke', setStroke), \
                                            ($fill, '#bg-color', '#no-fill', setFill)]:
        $colorPicker = $(idtag)
        $colorPicker.farbtastic(callback)

        # a function generator allows us to create a handler tied to current values, not the final ones
        # that the loop terminates with (useful for keeping color-pickers independent)
        makeHandler = def($target, $popup):
            showColorpicker = def(event):
                $colorPickers.hide()
                popupUnder(event, $target, $popup)
            return showColorpicker
        $swatch.click(makeHandler($swatch, $colorPicker))

        # now add a button that resets color to transparent
        makeReset = def($target, setFunction):
            reset = def():
                event.stopPropagation() # prevent colorpicker from showing
                setFunction('transparent')
                $target.css('background', '')
            return reset
        $(resetid).click(makeReset($swatch, callback))

    # set up top menubar
    popupUnder = def(event, $element, $popup):
        event.stopPropagation() # prevent document from closing us
        absolute = $element.offset()
        $popup.css({'left': absolute.left, 'top': absolute.top+$element.outerHeight()}).show()
    $menus = $('.menubar-menu')
    makeMenu = def():
        $this = $(this)
        idtag = $this.attr('id').split('-')[2]
        showMenu = def(event):
            $menus.hide()
            popupUnder(event, $this, $('#menubar-menu-'+idtag))
        $this.click(showMenu)

    # hide menus if we click anywhere outside the menu div
    hideMenus = def():
        $menus.hide()
        $colorPickers.hide()
    $(document).click(hideMenus)

    $menus.menu().removeClass('ui-widget')  # ui-widget adds ugly font, we don't want that
    $('.menubar-item').each(makeMenu)

    # attach actual functionality to menu items
    $('#menubar-menu-item-new').click(
        # start new drawing
        def(): dwg.clear()
    )

    $('#menubar-menu-item-export-to-image').click(
        # export drawing to PNG
        def():
            url = dwg.exportDwg()
            window.open(url, '_blank')
    )

    $('#menubar-menu-item-undo').click(
        # undo last action
        def(): dwg.undo()
    )

    $('#menubar-menu-item-redo').click(
        # redo last action
        def(): dwg.redo()
    )

    $('#menubar-menu-item-invert-colors').click(
        # invert colors
        def(): dwg.invert()
    )

    $('#menubar-menu-item-red-filter').click(
        # filter out red channel
        def(): dwg.redFilter()
    )

    $('#menubar-menu-item-green-filter').click(
        # filter out green channel
        def(): dwg.greenFilter()
    )

    $('#menubar-menu-item-blue-filter').click(
        # filter out blue channel
        def(): dwg.blueFilter()
    )

    $('#menubar-menu-item-darken').click(
        # make image half as bright
        def(): dwg.darken()
    )

    $('#menubar-menu-item-lighten').click(
        # make image twice as bright
        def(): dwg.lighten()
    )

    # pre-cache the element, so we don't have to 'select' it repeatedly
    $about = $('#about').dialog({'modal': True})
    $('#menubar-menu-item-about').click(
        # show info about this app
        def(): $about.dialog('open')
    )

    # disable right-click menu
    window.oncontextmenu = def(): return False

$(document).ready(main)

Ray-Tracing Example

Author: Salvatore DI DIO License: License: Creative Commons: Attribution + Noncommerial + ShareAlike

This demo shows Ray-Tracing done on a canvas element in RapydScript. This demo is based on logic described on this website. You can also play with this demo's source code live here.

rapydray.pyj

#
# Based on:
#http://viola.informatik.uni-bremen.de/typo/html/qingteng/index.html
#
# Documentation :
# http://www.scratchapixel.com
#
# Thanks to Alexander Tsepkov the creator of RapydScript, and
# Charles Law for allowing testing RapydScript online
# Post comments and suggestions on RapydScript Google Group
#
# You can modify the script and see the modifications
# in direct live (quite)
# When modifying you can reduce WIDTH and HEIGHT to avoid lag
#
# Authored by Salvatore DI DIO (email : salvatore dot didio at gmail)
#
# Best performance with Firefox
# On my mac book pro : 1280 x 800 rendered in less than a minute

WIDTH = 440
HEIGHT = 240

class Vector:
    def __init__(self, x=0, y=0, z=0):
        self.x = x
        self.y = y
        self.z = z

    def copy(self):
        return Vector(self.x, self.y, self.z)

    def length(self):
        return Math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)

    def sqrLength(self):
        return (self.x*self.x + self.y*self.y + self.z*self.z)

    def normalize(self):
        inv = 1.0/self.length()
        return Vector(self.x*inv, self.y*inv,self.z*inv)

    def negate(self):
        return Vector(-self.x, -self.y, -self.z)

    def add(self,v):
        return Vector(self.x + v.x, self.y + v.y, self.z + v.z)

    def sub(self,v):
        return Vector(self.x - v.x, self.y - v.y, self.z - v.z)

    def times(self, k):
        return Vector(k*self.x, k*self.y, k*self.z)

    def dot(self,t):
        return self.x*t.x + self.y*t.y+self.z*t.z

    def cross(self,w):
        v = Vector()
        v.x =  self.y * w.z - self.z * w.y
        v.y = -self.x * w.z + self.z * w.x
        v.z = self.x * w.y - self.y * w.x
        return v

    @staticmethod    
    def zero():
        return Vector()

class Ray:
    def __init__(self, vectOrigin, vectDest):
        self.origin = vectOrigin
        self.direction = vectDest

    def getPoint(self, t):
        # p = self.origin + t * self.direction
        return self.origin.add(self.direction.times(t))

class IntersectionResult:
    def __init__(self):
        self.sceneobject = null
        self.distance = 0
        self.position = Vector.zero()
        self.normal = Vector.zero()

    @staticmethod
    def nohit():
        return IntersectionResult()

class Sphere:
    def __init__(self, center = Vector(0,2,-1), radius = 1):
        self.center = center
        self.radius = radius
        self.name = "Sphere"

    def initialize(self):
        self.sqrRadius = self.radius * self.radius

    def double(self):
        return 100

    def intersect(self, ray):    
        v = ray.origin.sub(self.center)
        a0 = v.sqrLength() - self.sqrRadius
        DdotV = ray.direction.dot(v)

        if DdotV <= 0: 
            discr = DdotV * DdotV - a0   
            if discr >= 0:
                r = IntersectionResult()
                r.sceneobject = self
                r.distance =  -DdotV - Math.sqrt(discr)
                r.position = ray.getPoint(r.distance)
                r.normal =  r.position.sub(self.center)
                return r
        return IntersectionResult.nohit()

class Camera:
    def __init__(self,eye, front, up, fov,aspectratio):
        self.eye = eye
        self.front = front 
        self.RefUp = up
        self.fov = fov
        self.aspectratio = aspectratio

    def initialize(self):
        self.right =  self.front.cross(self.RefUp)
        self.up = self.right.cross(self.front)
        self.fovScale = Math.tan(self.fov * 0.5 * Math.PI/180)*2

    def generateRay(self,x,y):
        r = self.right.times((x - 0.5) * self.fovScale*self.aspectratio)
        u = self.up.times((y - 0.5) * self.fovScale)
        ray = Ray(self.eye, self.front.add(r).add(u).normalize())
        return ray

class Union:
    def __init__(self,geometries):
        self.geometries = geometries

    def initialize(self):
        for geo in self.geometries:
            geo.initialize()

    def intersect(self, ray):
        minDistance = Infinity
        minResult = IntersectionResult()
        for geo in self.geometries:
            result = geo.intersect(ray)
            if ((result.sceneobject != null) and (result.distance < minDistance)):        
                minDistance = result.distance
                minResult = result
        return minResult

class Plane:
    def __init__(self, normal, distance):
        self.normal = normal
        self.d = distance
        self.name = "Plane"

    def initialize(self):
        self.position = self.normal.times(self.d)

    def intersect(self, ray):
        a =  ray.direction.dot(self.normal)
        if a >= 0:
            return IntersectionResult.nohit()

        b = self.normal.dot(ray.origin.sub(self.position))
        result = IntersectionResult()
        result.sceneobject = self
        result.distance = -b/a
        result.position =  ray.getPoint(result.distance)
        result.normal = self.normal
        return result

class Color:
    def __init__(self, r=0, g=0, b=0):
        self.r = r
        self.g = g
        self.b = b

    def add(self,c):
       return Color(self.r + c.r, self.g + c.g, self.b + c.b)

    def times(self, k):
        return Color(k*self.r, k*self.g, k*self.b)

    def modulate(self, c):
        return Color(self.r * c.r, self.g * c.g, self.b * c.b)

Color.black = Color()
Color.red = Color(1,0,0)
Color.white = Color(1,1,1)
Color.green = Color(0,1,0)
Color.blue = Color(0,0,1)
Color.gold = Color(1,0.8745098039215686,0)
Color.cyan = Color(0,1,1)

class CheckerMaterial:
    def __init__(self, scale, reflectiveness):
        self.scale = scale
        self.reflectiveness = reflectiveness

    def sample(self,ray,position, normal):
        v =  Math.abs((Math.floor(position.x * 0.1) \
           + Math.floor(position.z * self.scale)) % 2)
        if v < 1:
            return Color.black
        else:
            return Color.white

lightDir =  Vector(6, 8.5, 15).normalize()
lightColor = Color(1,1,1)

class PhongMaterial:
    def __init__(self,diffuse, specular, shininess, reflectiveness):
        self.diffuse = diffuse
        self.specular = specular
        self.shininess = shininess
        self.reflectiveness = reflectiveness

    def sample(self,ray,position,normal):
        NdotL = normal.dot(lightDir)
        H = (lightDir.sub(ray.direction)).normalize()
        NdotH = normal.dot(H)
        diffuseTerm = self.diffuse.times(Math.max(NdotL, 0))        
        specularTerm =  self.specular.times(Math.pow(Math.max(NdotH, 0), self.shininess))
        return lightColor.modulate(diffuseTerm.add(specularTerm))

def rayTraceRecursive(scene, ray, maxReflect):
    result = scene.intersect(ray)
    if result.sceneobject:
        reflectiveness = result.sceneobject.material.reflectiveness
        mat = result.sceneobject.material
        color = mat.sample(ray, result.position,result.normal)
        color.times(1 - reflectiveness)
        if (reflectiveness > 0 and maxReflect > 0):
            r = result.normal.times(-2 * result.normal.dot(ray.direction)).add(ray.direction)
            ray = Ray(result.position, r)
            reflectedColor = rayTraceRecursive(scene, ray, maxReflect - 1)
            color = color.add(reflectedColor.times(reflectiveness))
        return color
    return Color(0,0,0)

CHECKER_PATTERN = True
def main():
    canvas = document.getElementById("canvas")
    ctx = canvas.getContext("2d")
    canvas.width = WIDTH
    canvas.height = HEIGHT
    w  = canvas.width
    h = canvas.height
    ctx.fillStyle = "rgb(255,255,255)"
    ctx.fillRect(0, 0, w, h)

    aspectratio = w/h
    camera = Camera(Vector(0,0.56,8),Vector(0,0,-1),Vector(0,1,0),30,aspectratio)
    camera.initialize()

    sphere = Sphere(Vector(1.5,0,-1),1)
    sphere.material = PhongMaterial(Color.red, Color.white, 500,0.25)

    sphere2 = Sphere(Vector(-1.5,0,-1),1)
    sphere2.material = PhongMaterial(Color.gold, Color.white, 500,0.25)

    sphere3 = Sphere(Vector(0,0,-2),1)
    sphere3.material = PhongMaterial(Color.blue, Color.white, 500,0.25)

    plane = Plane(Vector(0,1,0),-1)
    if CHECKER_PATTERN:
        plane.material = CheckerMaterial(0.1,0.9);
    else:
        plane.material = PhongMaterial(Color.cyan, Color.white, 500,1.0)

    scene = Union([plane,sphere,sphere2,sphere3])
    scene.initialize()

    maxDepth = 20

    imgdata = ctx.createImageData(w,h)
    #start
    for y in range(h):
        for x in range(w):
            sy = 1- y/h
            sx = x/w
            index = (x + y * w) * 4
            ray = camera.generateRay(sx, sy)
            result = scene.intersect(ray)
            if (result.sceneobject != null):
                col =  rayTraceRecursive(scene, ray, 4)
                imgdata.data[index + 0] = col.r * 255
                imgdata.data[index + 1] = col.g * 255
                imgdata.data[index + 2] = col.b * 255
                imgdata.data[index + 3] = 255
            else:
                imgdata.data[index + 0] = 0
                imgdata.data[index + 1] = 0
                imgdata.data[index + 2] = 0
                imgdata.data[index + 3] = 255

    ctx.putImageData(imgdata,1,0)

toggle = document.getElementById('toggle')
toggle.onclick = def():
    nonlocal CHECKER_PATTERN
    CHECKER_PATTERN = not CHECKER_PATTERN
    main()
main()

Stocks App Example

Copyright 2013 Pyjeon Software LLC
Author: Alexander Tsepkov
License: Creative Commons: Attribution + Noncommerial + ShareAlike

Preview

This is a web app for retrieving stock information from Google Finance and displaying it in a chart. The app also allows the user to modify the way stock data is presented. This app showcases:

  • Including stdlib as an import instead of a separate js file
  • Integration with Google Charts API
  • Ability to wrap/abstract entire APIs in separate classes (YQL and Google Charts)
  • Ability to create Pyjamas-like widgets that wrap around existing elements and extend their functionality (stock picker input widget, stock chart widget)
  • Ability to work with Flash (Google Charts API currently uses Flash for rendering)
  • Pulling data from outside sources via YQL (NASDAQ and Google Finance)
  • Asynchronous coding via RapydScript
  • RapydScript integration with RapydML and RapydCSS/SASS

stocks.pyj

######################################################
# Copyright 2013 Pyjeon Software LLC
# Author:   Alexander Tsepkov
# License: Creative Commons: Attribution + Noncommerial + ShareAlike
######################################################

import stdlib
import yql

class Stock:
    """
    Class responsible for tracking stock information. It handles tracking current stock name
    as well as performing the YQL query to retrieve stock data from Google and storing the
    data.
    """
    def __init__(self, symbols, callback=None):

        self.callback = callback

        # pre-cache these so we're not retrieving the element again on every blur/enter event
        $start = $('#start-date')
        $end = $('#end-date')
        onUpdate = def($event, ui):
            # select triggers before the update occurs, so we have to use 'select' name instead of .val()
            if $event.type == 'autocompleteselect':
                self.$widget.val(ui.item.value)
            startdate = $start.val()
            enddate = $end.val()
            self.get(startdate, enddate)

        self.$widget = $('<input></input>').autocomplete({
            'minLength':    2,
            'source':       symbols,
            'select':       onUpdate,
        })
        self.data = None
        self.$widget.blur(onUpdate)

        ENTER = 13
        onKeypress = def($event):
            code = $event.keyCode or $event.which
            if code == ENTER:
                onUpdate($event)
        self.$widget.keypress(onKeypress)

    def get(self, startDate, endDate):
        """
        get method for this stock, which retrieves info from google finance
        """
        name = self.$widget.val()
        if self.symbol == name and self.startDate == startDate and self.endDate == endDate:
            # don't trigger a refresh unless the parameters have been updated
            return
        self.symbol = name
        self.startDate = startDate
        self.endDate = endDate

        format = def(date):
            mmddyyyy = date.split('/')
            return '-'.join([mmddyyyy[2], mmddyyyy[0], mmddyyyy[1]])

        # google's historical data is only available in CSV format, but we need it in
        # JSON if we want to work with it in JavaScript. We will use Yahoo's YQL to
        # convert it. We will also need to create a callback function, since the request
        # is asynchronous
        onUpdate = def(query):
            if query['results'] is None:
                # bad symbol
                self.data = None
                self.$widget.css('background', '#fdd')
            else:
                self.data = query['results']['row']
                self.$widget.css('background', '')
            if self.callback is not None:
                self.callback(name, self.data)

        if self.symbol != '':
            # we will use csv version of the data to avoid dealing with unnecessary html
            url = 'http://www.google.com/finance/historical?q=' + name \
                + '&startdate=' + format(startDate) \
                + '&enddate=' + format(endDate) + '&output=csv'

            YQL('select * from csv where url="' + url + '"', onUpdate).fetch()
        else:
            # box is blank, don't try to request YQL for it
            self.data = None
            self.$widget.css('background', '')
            self.callback(name, self.data)

class StockChart:
    """
    This is a wrapper around Google Charts. It handles displaying and updating the chart,
    abstracting Google Charts away from the rest of the app. It also handles compiling
    the data in a representation Google Charts supports since the format of the API is
    somewhat clunky compared to other plotting tools like jqPlot. The only thing it doesn't
    wrap around is the loading logic for Google Charts API, unfortunately, which is due to
    awkward loading implementation on Google's part.
    """
    def __init__(self):
        $options = $('#chart-options')

        # create various technical indicator filters the user can play with
        self._filters = {}
        self._filterLogic = {}
        addFilter = def(name, callback):
            $button = $('<input type="checkbox" value="'
                    + name.replace(' ', '-') + '">' + name + '</input>')
            $options.append($button)
            self._filterLogic[name] = callback
            setFilter = def():
                self._filters[name] = $(this).is(':checked')
                self.redraw()
            $button.click(setFilter)

        normalize = def(cols, rows):
            # divide all stocks by their starting value so we can compare them better
            normalized = []
            orig = rows[len(rows)-1]
            for day, row in enumerate(rows):
                normalized.append([row[0]])
                for num, stock in enumerate(row[1:]):
                    normalized[day].append(stock/orig[num+1])
            return cols, normalized
        makeMovingAvg = def(name, days, ema):
            sum = def(a, b):
                return a + b
            return name, def(cols, rows):
                for idx, col in enumerate(cols):
                    if col[len(col)-1] != ')':
                        avgs = [{}]
                        moving = []
                        if ema:
                            alpha = 2/(days+1)
                            moving = rows[len(rows)-1][idx+1]
                        for row in reversed(rows):
                            if ema:
                                moving = alpha * row[idx+1] + (1-alpha)*moving
                                avgs.unshift({'col4': moving})
                            else:
                                moving.append(row[idx+1])
                                avgs.unshift({'col4': moving.reduce(sum)/len(moving)})
                                if len(moving) >= days:
                                    moving.shift()
                        self.add(col + ' (' + name + ')', avgs, cols, rows)
                return cols, rows
        rsi = def(cols, rows):
            # relative strength index filter
            tmp, ema15 = makeMovingAvg('', 15, True)
            for idx, col in enumerate(cols):
                if col[len(col)-1] != ')':
                    rsis = [{}]
                    ups = []
                    downs = []
                    prev = rows[len(rows)-1][idx+1]
                    for row in reversed(rows):
                        current = row[idx+1]
                        DATE = 0    # fake date placeholder
                        if current > prev:
                            ups.unshift([DATE, current-prev])
                            downs.unshift([DATE, 0])
                        else:
                            ups.unshift([DATE, 0])
                            downs.unshift([DATE, prev-current])
                        prev = current
                    tmp, ups = ema15(['up'], ups)
                    tmp, downs = ema15(['down'], downs)
                    for index in range(len(ups)):
                        if downs[index][2]:
                            rs = ups[index][2]/downs[index][2]
                        else:
                            rs = 100
                        rsis.append({'col4': 100-100/(1+rs)})
                    self.add(col + ' (RSI)', rsis, cols, rows)
            return cols, rows

        for name, fun in [\
                ('Normalize', normalize), \
                makeMovingAvg('15-Day SMA', 15, False), \
                makeMovingAvg('50-Day SMA', 50, False), \
                makeMovingAvg('15-Day EMA', 15, True), \
                makeMovingAvg('50-Day EMA', 50, True), \
                ('15-Day RSI', rsi)]:
            addFilter(name, fun)

        # this is an external API, so we have to use the 'new' keyword
        self.annotatedTimeline = new google.visualization.AnnotatedTimeLine($('#chart').get(0))
        self.clear()

    def clear(self):
        self._cols = []
        self._rows = []

    def add(self, symbol, data, cols=self._cols, rows=self._rows):
        if data is not None:
            if not len(cols):
                # this is the first symbol, create rows and append date
                for item in data[1:]:
                    rows.append([Date(item['col0']), float(item['col4'])])
            else:
                # rows have already been generated, just append to them
                for index, item in enumerate(data[1:]):
                    rows[index].append(float(item['col4']))
            cols.append(symbol)

    def redraw(self):
        # make a deep-copies of the data
        cols = $.extend(True, [], self._cols)
        rows = $.extend(True, [], self._rows)
        for key, val in dict.items(self._filters):
            if val:
                cols, rows = self._filterLogic[key](cols, rows)

        data = new google.visualization.DataTable()
        data.addColumn('date', 'Date')
        for col in cols:
            data.addColumn('number', col)
        data.addRows(rows)
        self.annotatedTimeline.draw(data, {})

def main():
    stockFields = []

    # declare a dummy method to avoid errors in case it is triggered before Google Charts finish loading
    updateChart = def():
        pass

    # load charting widget
    onChartLoad = def():
        nonlocal updateChart
        chart = StockChart()
        updateChart = def(symbol, data):
            chart.clear()
            for stock in stockFields:
                chart.add(stock.symbol, stock.data)
            chart.redraw()
    google.load('visualization', '1', {'packages':['annotatedtimeline'], 'callback': onChartLoad})

    # set up stock picker and dates
    triggerChange = def():
        $(this).change()
    $start = $('#start-date').datepicker({'onSelect': triggerChange})
    $end = $('#end-date').datepicker({'onSelect': triggerChange})
    $start.datepicker('setDate', -90)
    $end.datepicker('setDate', Date())

    symbols = []
    $stocks = $('#stock-input')

    # add new input widget
    newStock = def():
        symbol = Stock(symbols)
        $stocks.append(symbol.$widget)
        get = def():
            startdate = $start.val()
            enddate = $end.val()
            symbol.get(startdate, enddate)
        $start.change(get)
        $end.change(get)
        stockFields.append(symbol)

        # handle addition and removal of new widgets
        # new widgets will get added when there are no more empty widgets
        # empty widgets will get removed when there is more than one
        onWidgetUpdate = def(label, data):
            value = symbol.symbol
            isLast = symbol.$widget.is(':last-child')
            if isLast and value != '':
                newStock()
            elif value == '' and not isLast:
                stockFields.remove(symbol)
                symbol.$widget.remove()
            updateChart(label, data)
        symbol.callback = onWidgetUpdate

    # NASDAQ returns stock symbols in CSV format as well, YQL will convert them for us
    # it also seems to store companies from other stock exchanges, which makes work easier for us
    sync = 0
    exchanges = ['nyse', 'nasdaq', 'lon']
    onUpdate = def(query):
        nonlocal sync, symbols
        for symbol in query['results']['row']:
            symbols.append(symbol['col0'])
        sync += 1

        if sync == len(exchanges):
            # all exchanges loaded, remove duplicates and create stock picker
            $stocks.text('Stocks:')
            unique = def(element, index):
                return this.index(element) == index
            symbols = symbols.filter(unique, symbols)
            newStock()

    $stocks.text('Loading Symbols from Stock Exchanges')
    for exchange in exchanges:
        YQL('select col0 from csv where url="http://www.nasdaq.com/screening/companies-by-name.aspx?letter=0&exchange=' + exchange + '&render=download"', onUpdate).fetch()

$(document).ready(main)

Mailing List

This is a general group for anything RapydScript related, including questions, suggestions, comments, and showing your own projects.

RapydScript Mailing List

Community Projects

Flask RapydScript: Automatically compile RapydScript files when developing for the Flask Framework

RapydScript Decompiler: Convert JavaScript files to RapydScript

RapydScript Showcases: RapydScript user Salvatore DI DIO setup a showcase of RapydScript examples.

Vim RapydScript: RapydScript plugin for Vim

[Close]