├── .hgignore ├── LICENSE ├── README.md ├── bin └── rapydml ├── rapydml ├── compiler.py ├── html_colors.txt ├── lib │ ├── common.pyml │ ├── django.pyml │ ├── jinja2.pyml │ ├── rails.pyml │ └── web2py.pyml ├── markup │ ├── any │ ├── html │ └── html5 ├── markuploader.py └── util.py └── setup.py /.hgignore: -------------------------------------------------------------------------------- 1 | # use glob syntax 2 | syntax: glob 3 | 4 | *.pyc 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | RapydML is a Pythonic abstraction of XML/HTML, adding more 2 | functionality, ability to easily integrate it with any HTML 3 | templating framework, and create your own subset of XML whose 4 | rules will be enforced through RapydML compiler. 5 | Copyright (C) 2012 Alexander Tsepkov 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RapydML XML/HTML Abstraction Markup Language 2 | 3 | What is RapydML? 4 | ---------------- 5 | RapydML is a Pythonic abstraction for XML/HTML. The compiler generates HTML or HTML-like templates for Rails, Django, web2py or other frameworks (via templates) from indented, Python-like syntax. RapydML can also compile into arbitrary XML-like markup (such as SVG) based on a set of rules from user-defined template. These templates can have varying level of restrictions (compare the included lax HTML template against strict HTML5, for example). 6 | 7 | Note that RapydML is still in beta. Some features and syntax could change in the future. Feel free to contact us with your own suggestions, if you have any. 8 | 9 | 10 | Community 11 | --------- 12 | If you have questions, bug reports, or feature requests, feel free to post them on our mailing list: 13 | 14 | 15 | 16 | Installation 17 | ------------ 18 | You can download RapydML from the link at the top of the RapydML page on Pyjeon website () and extract it to a directory on your home computer. Alternatively, you can download it from our repository using the terminal directly (you will need Mercurial client installed): 19 | 20 | hg clone https://pyjeon@bitbucket.org/pyjeon/rapydml 21 | 22 | Afterwards, navigate to `rapydml` directory, and run the following command to perform the installation: 23 | 24 | python setup.py install 25 | 26 | This will install `rapydml` as a globally available command. Alternatively, you can also install RapydML using pip repository. 27 | 28 | 29 | Compilation 30 | ----------- 31 | Using RapydML is pretty straightforward. Assuming you have added an alias for it (or placed it in one of the directories covered by the system path), you can run it as follows: 32 | 33 | rapydml 34 | 35 | RapydML also allows you to use alternative XML-like syntax. If you decide to do so, you can place your file in `markup` directory inside of RapydML and it will automatically be pulled into RapydML. You can then compile your file using a different markup: 36 | 37 | rapydml --markupname 38 | 39 | RapydML already includes `HTML`, `HTML5`, and `ANY` markups you can use as examples. For more options and information, you can invoke rapydml's help: 40 | 41 | rapydml -h 42 | 43 | 44 | Getting Started 45 | --------------- 46 | RapydML is pre-parser that allows you to write HTML (or any other XML-like markup) in more readable, Python-like format. The parser then generates HTML for you, automatically closing/aligning different HTML tags (it can even be told to avoid closing tags for certain elements - like !DOCTYPE). It's similar to SASS/SCSS, but for HTML (similar concept to HAML). Like SASS/SCSS/HAML, RapydML generates the page before it's deployed, and not dynamically as it's being served, saving you CPU cycles. Take a look at the following HTML, for example: 47 | 48 | 49 | 50 | Welcome to my Web Page 51 | 52 | 53 |
54 | My Banner 55 |
56 |
57 | I haven't yet put anything on this page 58 |
59 | 62 | 63 | 64 | 65 | We can rewrite the above using RapydML as follows: 66 | 67 | html: 68 | head: 69 | title: 70 | "Welcome to my Web Page" 71 | body: 72 | div(id="title"): 73 | img(src="banner.png", alt="My Banner") 74 | div(id="content"): 75 | "I haven't yet put anything on this page" 76 | div(id="copyright"): 77 | "Copyright 2012 by Me" 78 | 79 | 80 | Loops and Code Reuse 81 | -------------------- 82 | We can pass in attributes as if they're arguments in a function call. We can already see that RapydML code is shorter and cleaner, but this becomes even more apparent in larger HTML files. Let us add a few images to our page to show that we support all major browsers, for example. We'll need to modify our content div as follows: 83 | 84 |
85 | I haven't yet put anything on this page 86 |

87 | Compatible with all major browsers: 88 | Firefox 89 | Chrome 90 | IE 91 | Opera 92 | Safari 93 |

94 |
95 | 96 | In RapydML we can accomplish the same thing with less repeated code using a loop: 97 | 98 | div(id="content"): 99 | "I haven't yet put anything on this page" 100 | p: 101 | "Compatible with all major browsers:" 102 | for $browser in [Firefox, Chrome, IE, Opera, Safari]: 103 | img(src="$browser.jpg", alt="$browser") 104 | 105 | But what if our alt attributes don't happen to match our image names? That can be remedied as well, by grouping variables via sub-arrays: 106 | 107 | div(id="content"): 108 | "I haven't yet put anything on this page" 109 | p: 110 | "Compatible with all major browsers:" 111 | for $item in [[firefox, Firefox], [chrome, Chrome], [ie, Internet Explorer], [opera, Opera], [safari, Safari]]: 112 | img(src="$item[0].jpg", alt="$item[1]") 113 | 114 | 115 | Variables and Sequences 116 | ----------------------- 117 | Notice how we declare variables in RapydML. All variables must be preceeded by $, like in perl. While awkward at first, this actually allows us to more easily distinguish variables from HTML tags, and allows us better syntax highlighting of variables, something Python doesn't have. The assignment operator is := instead of the typical = sign. This is to avoid clashing with HTML's = sign if it happens to be part of the variable's value, as shown in example below: 118 | 119 | $src := src="smiley.gif", alt="A Smiley" 120 | for $i in [0:100]: 121 | img($src) 122 | 123 | We don't need to quote whatever we're assigning to the variable, the leading/lagging whitespace will automatically get stripped, the whitespace in the middle will get preserved. Note the use of a shorthand to create an array of 101 consecutive integers. This shorthand works similar to Python's range() function. The first argument specifies the minimum, the second specifies the maximum (not maximum+1 like Python's range()), the third argument is optional and represents the step size. Here are some examples: 124 | 125 | [0:6] -> [0,1,2,3,4,5,6] 126 | [1:8:2] -> [1,3,5,7] 127 | [8:1:-1] -> [8,7,6,5,4,3,2,1] 128 | [8:1] -> [] 129 | 130 | 131 | Methods and HTML Chunks 132 | ----------------------- 133 | If you find yourself reusing the same HTML in multiple places, but not in sequential order (preventing you from using a loop), you can define a method, which you can then invoke. A RapydML method is a chunk of HTML, which then gets plugged in every time the method call occurs. For example, here is how we could create a navigation menu, reusing the same button logic: 134 | 135 | def navbutton($text): 136 | div(class='nav-button'): 137 | img(src="$text.png", alt="Navigate to $text") 138 | 139 | div(id='nav-menu'): 140 | navbutton('Main') 141 | navbutton('Products') 142 | navbutton('Blog') 143 | navbutton('About') 144 | 145 | There is another subtle, but extremely useful feature methods have. Method calls can have child elements. These children will get appended inside the last outer-level element declared inside the method. For example, if you're creating a page with multiple sections, and the content layout inside each section differs, you can do the following: 146 | 147 | def subsection($title): 148 | div(class="sub-section"): 149 | $title 150 | div(class="section-content") 151 | 152 | div(id="main"): 153 | subsection('Foo'): 154 | 'This is the foo section, it only has text' 155 | subsection('Bar'): 156 | img(src="image.png") 157 | div: 158 | 'Bar section has images, and sub-children' 159 | 160 | Both, Foo and Bar, will append the children inside of the `section-content` div. This is a very powerful feature, allowing method content not only to get dumped inside of your page, but also wrap around other content in your page - similar to Python decorators. There is even more to this feature, which you can read about in the `Advanced Usage` section. 161 | 162 | 163 | Math and String Manipulation 164 | ---------------------------- 165 | Like many other abstraction languages, RapydML can do a lot of the common logic for you. It has no concept of unit conversion like SASS, but it can easily handle many other things, like string manipulation, arithmetic, and mixing colors. 166 | 167 | 168 | ### Arithmetic and Colors 169 | 170 | RapydML can handle basic arithmetic and color computations for you. For example, let's rewrite the above logic such that our navigation buttons start with dark blue and get brighter with each button: 171 | 172 | $color := #004 173 | def navbutton($text): 174 | div(class='nav-button', style="background: $color"): 175 | img(src="$text.png", alt="Navigate to $text") 176 | 177 | div(id='nav-menu'): 178 | for $i in ['Main', 'Products', 'Blog', 'About']: 179 | $color += #222 180 | navbutton($i) 181 | 182 | Note that `$color` needs to be updated outside of a function. This is because RapydML shadows function variables, like Python. Moving `$color += #222` inside the function will update a local copy of `$color`, which gets discarded after the function terminates. RapydML is smart about colors. You can use the regular `#RRGGBB` format, shorthand `#RGB` form, or even html-accepted color name like `"Brown"`. When using html colors, it's important to use double-quotes around the color, otherwise it will be treated as a regular string. The color name itself, however, is not case sensitive, you can use "DarkBlue" or "darkblue", for example, or even "dArKbLuE". If no color with that name exists the string will be treated as a regular string. Mathematical computations are not limited to colors. You can also use them to compute dimensions of HTML elements, perform computations that get output on the page, or even concatenate strings: 183 | 184 | $height := 100 185 | $padding := 2 186 | $total := $height + $padding + 1 # border width of 1 187 | 188 | $name := 'smiley' 189 | $imgurl := 'static/images/' + $name + '.png' 190 | 191 | You probably noticed the use of `+=`, which is a shorthand for incrementing. Here are a few other ones: 192 | 193 | $a += 2 # equivalent to: $a := $a + 2 194 | $a -= $b # equivalent to: $a := $a - $b 195 | $a *= $b*2 # equivalent to: $a := $a * ($b*2) 196 | $a /= 5 + 2 # equivalent to: $a := $a / (5 + 2) 197 | 198 | Lastly, there is a caveat about color arithmetic. While RapydML will limit min/max colors to be within allowed range, the color get converted to an integer when performing operations. This means that if you keep adding blue to a color that already reached maximum allowed amount of blue, RapydML will start adding green. This behavior could be modified in the future such that each channel is independent. 199 | 200 | 201 | ### Using Python Directly 202 | 203 | RapydML will handle simple arithmetic and string concatenation for you, but what if you require more complex logic? RapydML has access to the full firepower of python (or at least its stdlib, you can't import Python modules). For example, let's say we wrote the following function to generate a button linking to one of the social media websites: 204 | 205 | def socialMedia($name): 206 | div(class="social-media"): 207 | a(href="www.$name.com"): 208 | img(src="$name.png" alt="$name") 209 | 210 | The only problem is that the content of `$name` has to be in lowercase for the link to work. You could rename your PNG image to be in lowercase as well, but the `alt` text in all lowercase would look bad. Fortunately, RapydML can invoke Python's native logic if you use `python` prefix. Let's rewrite the above logic to capitalize the displayed text: 211 | 212 | def socialMedia($name): 213 | div(class="social-media"): 214 | a(href="www.$name.com"): 215 | img(src="$name.png" alt=python.str.capitalize("$name")) 216 | 217 | Likewise, we could have instead used `python.str.lower()` if the `$name` was already capitalized before being passed into the function. RapydML compiler also auto-imports `math` module, allowing you to use logic like `python.math.sin(1)`. The `python.` prefix is not needed for Python methods that are nested inside other Python methods, like the following example: 218 | 219 | python.math.sin(min(1, 2, 3, 4)) 220 | 221 | 222 | Using Raw Text, HTML, or JavaScript 223 | ----------------------------------- 224 | As you probably already noticed, none of the above features address the possibility of having non-XML-like data inside of your page. There are times, however, when you might want raw text on your page that doesn't get interpreted. For example, you might need to embed JavaScript or CSS directly inside of your HTML, or might want to insert a raw chunk of HTML inside a tag when you can't get the markup just right via RapydML. 225 | 226 | 227 | ### Verbatim Method 228 | 229 | There were multiple alternatives I looked into for this, in the end I settled on one that seemed cleanest. RapydML introduces a verbatim() method, which if specified for the tag, will treat everything indented under the tag as raw text, putting it inside the HTML page as is. For example, let's add a Javascript tag. We can do it a couple different ways: 230 | 231 | javascript = verbatim('') 232 | 233 | The second way, since we already have a 'script' tag defined for us by default that works exactly like we need to, is to pass in an existing tag to use as a template: 234 | 235 | javascript = verbatim(script(type="text/javascript")) 236 | 237 | Now, should we choose to add raw JavaScript to our code, we can write it as follows: 238 | 239 | body: 240 | h1: 241 | 'Page Title' 242 | javascript: 243 | function factorial(n) { 244 | if (n === 0) { 245 | return 1; 246 | } 247 | return n * factorial(n - 1); 248 | } 249 | div: 250 | label: 251 | 'Text goes here' 252 | 253 | As soon as indentation resets to same level or higher as the verbatim tag, the raw JavaScript stops, and we start interpreting tags again. There is also verbatim_line() method available, that works the same way, but condenses the code into a single line by replacing `\n` with spaces and removing indentation. This is useful when you want to compress the chunk of raw code into a single line. 254 | 255 | 256 | ### Variables Inside of Verbatim Blocks 257 | 258 | It would be nice if the logic inside verbatim blocks would never need access to outside information. In real life, however, that's not always the case. Imagine that we need to include a chunk of JavaScript for creating a JSON call inside of our page. The format of this JSON call is identical across all of our pages on the website, with the only difference being the name of the page requested. The RapydML for this might look something like this: 259 | 260 | javascript: 261 | $(document).ready(function() { 262 | $.getJSON('call/json/get_docs/True?owner=pyjeon&repo=$page', function(html_docs) { 263 | $("div#documentation").append(html_docs); 264 | }); 265 | } 266 | 267 | The problem here, however, is that since `javascript` was defined as a verbatim tag, the variable `$page` will not get replaced. One work-around is to add this JavaScript to each page, modifying the value of `$page` individually for each use. Surely, there must be a better way. There is, RapydML allows you to specify which variables are to be replaced on per-tag basis. You can specify them as arguments for the verbatim tag: 268 | 269 | javascript($page): 270 | $(document).ready(function() { 271 | $.getJSON('call/json/get_docs/True?owner=pyjeon&repo=$page', function(html_docs) { 272 | $("div#documentation").append(html_docs); 273 | }); 274 | } 275 | 276 | Now every occurence of `$page` within the verbatim tag will be replaced. Note that the variables only apply to the current instance of the tag, and that only the specified values will get replaced: 277 | 278 | $foo := "one" 279 | $bar := "two" 280 | 281 | javascript: 282 | var a = $foo + $bar; // var a = $foo + $bar; 283 | 284 | javascript($foo): 285 | var a = $foo + $bar; // var a = "one" + $bar; 286 | 287 | javascript($bar): 288 | var a = $foo + $bar; // var a = $foo + "two"; 289 | 290 | javascript($foo, $bar): 291 | var a = $foo + $bar; // var a = "one" + "two"; 292 | 293 | 294 | ### Using Other Languages 295 | 296 | Verbatim tag is convenient when you want to include raw JavaScript inside of your page. But what if you're using RapydScript, CoffeeScript, or something else for your JavaScript? The typical way to include those is by having the `