- Rebol Idioms and Puzzles
- Plain text to HTML.. in one line
- Look ma, just one face!
- It's Alive!!!
- Basic Make-doc
The Zine strides forward with jets blazing and all seven of its legs whirling with streams of sticky flame blasting forth from its three eyeballs! The Zine can not be stopped! Rebol/Zine is chewing up the countryside and scooping up the buildings in its granite crushing jaw! Quick, everybody climb onto the Zine's armor covered back! Yes! Run, hurry, send in your contributions! The Zine's appetite is enormous! It snacked on Kansas, and guzzled up the South Pole! It's using the Eiffel tower to clean its nine rows of fangs! Wavy rays of Rebol wisdom flows forth from its forehead like with pulses of thunder! Aaaaaaarhhh!! the Zine is swallowing quasars, drop kicking red giants, flicking away Jupiter with a laugh! Be brave, and join the mighty and unstoppable zine, send in your article today!
Okay, well that's the bad science fiction intro. Enjoy!
Rebol Idioms and Puzzles
Rebol Header is alive
A lesser known fact about Rebol scripts:
LOADing a script makes an object out of the header. Try creating the following file and loading it at the console:
print ["I was loaded" now/time/precise]
Title: random "XYZ123"
set 'headr self
1 2 3
Rebol headers can be smart objects if you want them to be.
Now this leads to a question you might have. "What if I don't want a Rebol header to evaluate when I LOAD it?" Use LOAD/all. LOAD/all will treat the header as a word (Rebol) and a block. In fact, LOAD/all is the safest LOAD and you should use it when ever LOADing a string or file from an untrusted source (like CGI, for instance).
Also notice the following difference between LOAD and LOAD/all:
LOAD/all will always give you a block where as LOAD will give you a single item if there is only one item. LOAD/all always produces a block as a convenience because it is the "paranoid" LOAD. Whatever you give LOAD/all, it always gives you an unevaluated block of that thing. So if you do:
error? try [load/all some-random-string]
you can't go wrong. LOAD/all you can.
Plain text to HTML.. in one line
By Chris Page
Possibly the shortest /zine article to date
One of my scripts must take plain text (or more or less plain text) from a database and show it on a web page. I didn't want to force users to use HTML to create new paragraphs - users use two newlines in normal text, they don't want to be fiddling around with <P> or <BR> tags.
Thankfully, Rebol's parse command can do all the work for me:
>> parse mytext [
some [to "^/^/"
(remove/part mark 2 mark: insert mark "<P>^/^/</P>")
This parse rule takes a piece of text and scans it for two successive newlines. When it finds one it marks the position, removes the newlines, inserts paragraph end and start tags and then continues the parse just after the new insertion. I have split the parse rule over several lines to aid readability, but you could keep it all on one line: Plain text to HTML, in one line.
Look ma, just one face!
The following example is rather stupid and mostly useless but it is merely meant to stretch your imagination. It may also, in the process, give some understanding of the PANE function that is used to define an iterated face.
So, the example below is marvelously insane. It's an iterated face but hardly in the normal usage as the iterated face changes styles, actions, colors, effects, position, and more all in the hopes of pretending to be an entire layout all on its own. Nothing really prevents you from doing this except logic which I am lacking at the moment.
The Pane Function
The code has a layout where it defines a single face, a box. However, this box has the PANE field defined as a function instead of as a block of sub-faces. This turns it into an iterator. The PANE function is defined:
pane: func [face offset] ["body"]
where FACE is the parent face which holds the iterated face and OFFSET is either a pair! or an integer!. If pair? OFFSET, then the pane function should return an integer which is an index to the iterated face that the offset is over. The most common iterated face is the LIST where all iterated faces go down the screen so the one at the top is 1, then 2, etc. depending on the height of the iterated face and if the faces are scrolled at all. this calculation is up to the pane function to make. If integer? OFFSET, then the pane function returns the iterated face modified in any way for that index. Often the only modifications are the offset and text of the face. NONE should be returned if an OFFSET is passed in that does not match any face at all.
The PANE function below changes almost every attribute of the iterated face. Running the code, you will also notice strange behavior with the buttons as they seem to affect each other. This is one of the reasons that you do not build entire interfaces with a single iterated face.
Despite the odd buttons, we have managed to create a layout with a title, a weird colorful button, a check box, some text, and an image with buttons that move it up and down in 50 lines of code. Plus it's just one face! OK, technically speaking it's three faces: one for the main window, one for the box (the iterator), and one for the iterated face itself. The title sounds better with a "one" instead of "three."
As discussed in the last episode of the Zine, fields cannot be iterated which is why none show up in this little interface. Some restrictions do apply to insane use of iterators, void where prohibited, etc. etc. etc.
Title: "An iterator in disguise"
iter-face: make get-style 'button 
bedge: make iter-face/edge 
bfont: make iter-face/font 
bfeel: get in get-style 'button 'feel
[0x0 300x36] [0x50 180x40] [212x200 176x44]
[0x100 16x16] [20x100 180x20] [190x205 16x16]
[190x225 16x16] [500x0 1x1]
in-region: func [oset start size] [
all [oset/x >= start/x oset/y >= start/y
oset/x <= (start/x + size/x) oset/y <= (start/y + size/y)
logo: load http://www.rebol.com/graphics/reb-logo.gif
view layout [
myface: box 400x400 with [
pane: func [face oset] [
if pair? oset [
repeat x length? regions [
if in-region oset regions/:x/1 regions/:x/2 [return x]
if oset > length? regions [return none]
iter-face/feel: make face/feel 
iter-face/font: make bfont 
iter-face/edge: make bedge 
switch oset [
iter-face/text: "It's just one face!"
iter-face/edge: make edge [size: none effect: none]
iter-face/effect: compose [
gradient ((random 2x2) - 1x1)
(random 255.255.255) 0.0.0
iter-face/text: "I'm colorful!!" iter-face/feel: bfeel
iter-face/effect: [fit colorize 195.0.0]
iter-face/edge: make edge [size: none effect: none]
iter-face/edge: make get in get-style 'check 'edge 
iter-face/feel: get in get-style 'check 'feel
iter-face/text: "<--- Look! A check box."
iter-face/effect: [fit arrow]
regions/3/1: regions/3/1 - 0x5 show myface
iter-face/effect: [fit arrow rotate 180]
regions/3/1: regions/3/1 + 0x5 show myface
8  ; this is here because is hides another
; weird behavior of the whole button problem
There are a few things I hope you can walk away from this with:
- A better understanding of iterated faces.
- The knowledge that iterators are wild crazy beasts and should not be overlooked as an alternative to very strange layouts of static items. (buttons and entry fields are not suggested)
- A good reason to start using the phrase "Life is like the Rebol/Zine..."
Do you know the feeling that you need to get there from here? Or even worse: that you need to get something from there to someplace else? And that digitally, too? And it needs to be easy, fast, secure, out of the box because there is a zillion other things to do?
Enter Lightweight Distributed Computing (LDC).
Since Rebol networking is so easy, and you can mix data and code so easy, it is straightforward to send a message to another Rebol process and get a result back. The View desktop does it all the time! That's LDC.
But once you start doing it yourself, you are faced with some problems compared to simply sending and do'ing the message:
- On the receiving side: is the request allowed and safe to do?
- Is the message integrity there?
- Can the message be encrypted?
- Can access be limited to some set of IP-numbers (possibly trusted)?
Rugby is a Rebol framework designed from the ground up by me (Maarten Koopmans, m.koopmans2 dot chello dot nl) to do all these things and make them as easy as one line of code.
You can find rugby as rugby3.r in the rebol script library.
(On the REB)
So what does Rugby actually allows you to do? "Rugby allows you to expose any set of global commands in one line of code to other Rebol processes."
A simple sample of Rugby usage, with the incredible useful function "echo" that returns a compressed version of whatever you put in.
echo: func [ in [string!]] [ return compress in]
You can make this incredible service available with the following line of code:
serve [ echo ]
The Rebol process now enters "network listen and serve" mode, and your first Rugby server is live! But... you want to use it. Say you started the service on foo.bar.com:8989 and want to have you name echo'ed:
You simply do:
my-val: rexec/with [ echo "Joey Cool"] foo.bar.com:8989
my-val will contain the compressed value now. Easy does it!
So what's about all that serving stuff? How do you do that, and possibly secure? There are two ways to serve functions to other Rebol processes namely serve and secure-serve. Secure-serve does exactly the same as serve but with blowfish encryption on the messages, for Command and Pro users only. I will describe serve and you can substitute secure-serve if you want to.
As said before, serve allows you to expose a set of global functions to other processes. Only these functions are exposed! This prevents malicious hackers from destroying Rebol processes, or worse. Serve has a /restrict refinement, that allows you to specify a block of trusted IP addresses. Only requests from these addresses will be served then.
So using serve is a 4 step process
- Write your code as usual.
- Decide what you want to serve and expose it as top level functions.
- Determine if you want encrypted or not (Pro and Command only), and if you restrict access.
- Start serving.
One note about serve: I/O is read an written in blocks, but you function is done "all at once". This may give blocking behavior if your served function takes a lot of time. Divide and conquer (possibly using more Rugby).
Client side Rugby.
That's an easy one:
You either use rexec or sexec. These are shortcuts for secure-exec and remote-exec, respectively. You give a block that simply has the function name and its arguments:
rexec [ reverse [ one two]]
will return [ two one ] provided that reverse is served.
By default they connect to localhost:8001. To connect to a different machine, do:
rexec/with [ reverse [ one two ] make port! tcp://la.lo.com:8001
Same goes for sexec. Sexec does automatic key strength negotiation, so export and full versions of Command and Pro can communicate.
Rexec has one other nifty feature: you can have deferred and one-way requests. Sexec is currently lacking this.
rexec/oneway [ quit ]
sends the quit command and does not wait for a return value.
rexec/deferred [ reverse [one two]]
returns an id, that you put in a word. It returns immediately, so you can check back later for the result using either wait-for-result id which blocks until there is an answer, or poll-for-result which returns false or the return value.
Sometimes you want to have client and servers in one process, for example in a file sharing utility. Using View (only) you can do this easily.
Just write you script as you would normally do, creating your functions, rexec's and your layouts. In the end you start viewing and serving, in that order.
serve [ ... ]
rugby-view is the same as view, but without starting the event loop, which is done by serve.
So... on to your first P2P program from here!
You can contact me @ m dot koopmans2 at chello dot nl
Enjoy Rugby and let me know what you think
By Chris Page
Dr Frankenbol's Monster
If my recent /zine articles haven't bored you to tears you will know that I have been doing a lot of work relating to email robots. This article can be considered a "post-mortem" of my robot project. While this may not be of direct relevance to any project you are working on, I hope the subjects I will cover may come in useful someday. Some of this stuff may seem obvious to the Rebol Gods, but even though I've been using Rebol for over a year, I'm still learning. ;)
Overall my robot project turned out, after several false starts, to be a success. It is now working and I have made a number of updates to my website using it. Several more advanced features have yet to be implemented, but already it can handle attachments, unpack files, copy files around the site, delete files and add entries to the mySQL based news log. And all this was achieved without the aid of a hunch-backed assistant, a thunderstorm or a mob of angry villagers!
Reinventing the wheel
Soon after my first zine article, which discussed the basic ideas underlying the whole email/robot/website escapade, I discovered that much of the matter I had discussed was little more than reinvented wheels bolted to a slightly different cart: Rebol has built-in email parsing facilities far superior to my rather simplistic parse rules. By using the import-email command I could transform a buffer containing an email as a string into an object from which things like the sender, subject and so on could be obtained without any effort. Using import-email also allowed me to use the mime parsing scripts available from various sites, opening up the possibility of processing attachments. I eventually ended up using a version of the demime.r script written by Brett Handley (and later modified by Mario Cassani) which I slightly modified (I used this script mainly because the alternatives were overkill for what I needed)
One slightly annoying problem I found with import-email is the way in which it translates the emails in the from: into email! objects, dropping the sender name in the process. For example, if I send an email to the robot from "Chris <email@example.com>", the from: in the object created by import-email is "firstname.lastname@example.org". While this is not a problem for most email related tasks, I needed to use the name in part of my access controls, so in the end I needed my simplistic parse rule anyway! :(
Any Port in a storm
My next major mishap involved reading from stdin. My original discussion used the rather brain-dead
>> buffer: make string! 15000
>> read-io system/ports/input buffer 15000
to read from stdin into a buffer. This is not only wasteful, but it would mean that the robot could only handle small attachments. There had to be a better way to do it..
My second attempt used copy system/ports/input to read stdin. While this worked, each line was in a separate string in a block - obviously no use if I was going to plug the input into import-email. Thanks to the mailing list, I finally hit upon:
>> set-modes system/ports/input [lines: false binary: false]
>> buffer: copy system/ports/input
This reads all the data on stdin into a string, perfect for stdin. The only problem I can see with this solution is that, if a very large email was send to the robot it could cause problems on the server. But as yet I have found no other way to get all valid emails while avoiding problems - I suppose that copy/part system/ports/input 2000000 would go some way to solving this.
Trapping the fercilicious Error
Until I started my robot project the most complex error handling I had ever done was little more than
either error? part: try [ foo/bar ] [
Send a page
process foo ; in some way
but the robot needed to do more. Part of the command interpreter the robot contains needs to evaluate a block from the email body. If the body contained an error it would create a script error when I tried to evaluate it. While I could just let Rebol bomb out with an error, and let the mail software take care of informing the sender of this, I though this was a far from elegant solution.
Unfortunately, when I tried using "normal" error trapping Rebol still bombed when I tried to print the error id. I could have just told the sender than there was an error in the command body, but a more useful error message would be preferable. After asking about this problem on the mailing list, Tim Johnson and Romano Paolo Tenca gave me an answer:
either error? body-block: try [do body-text] [
body-block: disarm body-block
print "Error in command data: "
print reform bind (
get in (get in system/error body-block/type)
body-block/id ) in body-block 'type
][ . . .
This allows me to give back something more meaningful!
One interesting problem I faced along the way concerned the whole issue of collecting the feedback from various options to send back to the user. Most of the scripts I have written or used (like demime.r) use print or prin to display progress via the console. This is not suitable for an email robot so I needed to collect all the feedback into a buffer which could be sent in an email back to the user. In other languages I may have had to go through each script replacing the prints with appends. Rebol has a far more tidy solution - just replace print:
print: func [
"Replacement for print, redirects prints to buffer rather than out"
append worklog rejoin [ reform out "^/"]
prin: func [
"Replacement for prin, redirects prints to buffer rather than out"
append worklog reform out
Now all the robot needed to do was send worklog back to the user, no modification of the other scripts was necessary.
So when do you get to see it?
As I said at the start of this article, my robot is now working on my website and undergoing irregular upgrades as I implement functions. Since I have used several scripts written by other Rebolutionaries, it is only fair that I give back. Once I have the robot up to a fully operational state I will release the complete source to the Rebol community. At present there are still some dirty hacks in there along with some sensitive security related information (passwords, addresses etc). I will need to remove these elements before releasing the code, but I hope to have a basic release in the next month or two. I will then make the code available on my website for anyone to download and use.
The robot would not be working if it wasn't for the help of the Rebol list in general and the following individuals in particular. You all have my thanks!
DocKimbel - MySQL protocol needed for the news code
Brett Handley, Mario Cassani - MIME extraction code
Bohdan Lechnowsky - Large file copying
Johan Forsberg - Discordian dates used in the robot ;)
Tim Johnson, Romano Tenca - Thanks for help with the errors
Ryan Cole, Paul Tretter and
others I don't have recorded (sorry!) - help with the stdin problem
- Chris chris at starforge dot co dot uk
Make-doc is a really cool tool that will make your life a million times easier. Here's a simple document, just to show the basic approach to creating a nice hierarchical document:
Zine Gobbles Galaxy
Author: Garth Rebola
Comment: This is the "Boiler plate"
===Eating habits of Zine (Heading)
Some more text. Some more text. Some more text. Some more
text. Some more text. Some more text. Some more text. Some more
---Andromeda for Dinner (Subheading 1)
Some more text. Some more text. Some more text. Some more
text. Some more text. Some more text. Some more text. Some more
a code example [
indented will be bold [
;-- In the Zine source, this whole
; thing is indented as code
+++It can't be stopped! (SubSubheading)
#Numbered point one
#Numbered point two
:definition - It's going to eat the universe!
===That's it! (Heading 2)
Run make-doc on the above and you have a complete, nicely formatted document with a navigation heading. That's a pretty basic example, but in the next episode we'll cover make-doc in greater detail and look at some of the more advanced aspects of the dialect.
We use make-doc to assemble the Zine, and if you get the email version, you're getting the make-doc source code. Haha— it's clean enough that it can be sent as the email text version with out modification. This also means that anyone can modify make-doc and spit out their own formating of the Zine. Many neat benefits to something so simple — just like Rebol.
You can find make-doc at:
There's also a tutorial on the REB.
Correction: The first Zine had an article about mail filtering. There was a bug in the code for exporting to mbox. The days and months shouldn't be taken from system/locale but need to be defined as blocks like:
["Mon" "Tue" ...]
["Jan" "Feb" ...]
The mbox code in the script library (where it was originally taken) was correct, but it was changed at the end with out testing, which is a big no no and the responsible party has been thoroughly pummeled for his mistake (me).
Send in the contributions!!!!! email@example.com
Anyone who wants to help publish the Zine, contact us. We can always use more folks taking part in this system. So far it's been just sort of scraped together when the day arrives. It would be neat if it had a process of some form so it wasn't all chaotic.. but if not, that's okay. Chaos is self-organizing and thereby creative, staving off entropy and folding in deep structure. Yehaw!