Rebol/Zine Issue 1 Vol 1
- Rebol Puzzles and Idioms
- Posting to Web Forms Tip
- HTTP Tips and Tricks
- ALERT ALERT ALERT
- Command Tricks
- POP to mbox Filter
- A Day in the Life of Rebol
Welcome to the first episode of Rebol/Zine! Hopefully you will enjoy the articles found in this little Zine. It's been a joy to put this first episode together and I hope that many more will come.
Programmers have queues. These queues are filled with tasks and projects and things we'd like to get done and many times our queues stretch for miles. There's so much to create with our brains and our time and best efforts. A programmer's queue has a unique property: It's sometimes hard to add things into the queue but things seem to fall out of the queue with great ease!
Anyhow, I hope that people will want to add the Rebol/Zine to their queues, send in their comments, corrections, flames, etc. We'll begin a mail section in later issues. And even more importantly, please send us your contributions!! We want your tips, tricks, editorials, novellas about Rebol, you name it. You don't have to submit articles in English either. We'll try and get translations later.
Mail your contributions, comments, corrections, bug reports, etc to:
[This episode did not receive thorough editing, so please forgive any grammar or typographical errors you find. ]
Rebol Puzzles and Idioms
By Jeff Kreis
A very handy Rebol construct is the function UNTIL combined with the lazy evaluators ANY and ALL along with NOT. These can be combined to very easily express many types of problems you may face in Rebol scripts. You want to run UNTIL ALL, or UNTIL ANY of the conditions are met. Likewise you want to continuously loop the code UNTIL NOT ALL the conditions are met, or UNTIL NOT ANY have been met.
Below are some examples:
go-until-something-happens: does [
not empty? read pop://user:email@example.com
now/time = 00:00:00
port: wait [listen-port :0:01]
This example will run until either some-file.dat exists, some mail is received at a pop mail account, it turns midnight, or something connects to a 'listen-port'. The WAIT is important, because it will prevent this code from "busy looping", exhausting your computer's resources spinning away in a tight loop. A one second waiting period between each loop is plenty of time for most operating systems to deal with all the other processes going on. The WAIT will return NONE if it times out before anyone connects to the listen port, and a NONE will make the loop continue.
retry-mail: func [to [email!] msg [string!]][
not all [
error? try [send to msg]
not confirm "Error sending. Try again? "
The above example, as you would expect, keeps trying to send the message. If it succeeds the loop ends, otherwise it asks if you'd like to try again. If you say no, then ALL fails, which means that NOT ALL is true and UNTIL is then satisfied with the result.
You can, of course, use UNTIL and the lazy evaluators to pass back meaningful data:
get-someone-on-the-line: does [
return until [
You'll have to implement CONNECT-TO, though. The above example will return the first connection that can be established to either Bob, Mary, or Fred. This is an artificial example, but there's many real world situations that are similar. It's interesting to note that you usually think of RETURN as immediately sending back some value, but in this case RETURN may take an indefinite amount of time before it actually DOES RETURN a value.
Here's a simple tip:
Blocks can be placed inside other blocks. We all know this, but one interesting thing is that the same block can be placed within another block offset to various locations. For example:
house: copy 
rooms: [1 2 3 4 5 6 7]
forall rooms [
append/only house rooms
] rooms: head rooms
Above we've placed the same block (rooms) inside house at seven different offsets. By doing this, we did not create not seven copies of rooms, but seven separate references into the block rooms.
We can see this by changing some of our rooms.
forall house [
change/only house/1 join "room-" house/1/1
] house: head house
["room-1" "room-2" "room-3" "room-4" "room-5" "room-6" "room-7"]
Above, we've change the state of rooms through the house block. Holding the rooms block at various offsets allows us to easily change it. We can also change alter our container structures (house in this case) with out altering what's contained (rooms). Here we flip our house around without disturbing our rooms:
foreach room house [prin [room/1 #]]
print [newline rooms]
The above will print:
room-7 room-6 room-5 room-4 room-3 room-2 room-1
room-1 room-2 room-3 room-4 room-5 room-6 room-7
In fact, we can continue this scheme in the rooms. Let's add space in these rooms to put things and create doors which lead to other rooms:
foreach [room door][
1 7 2 6 3 5 4 4 5 3 6 2 7 1
change/only room: at rooms room reduce [
room/1 copy  at rooms door
Okay, that looks a little crazy, but what we're doing is changing each element of rooms to the following: A block containing the name of the room (which we made before), a new block which is a place to put things in the room, and a "door" which is a reference to another room. For example, room 1 has a door to room 7. Room 4 has a door that goes to 4!
Alright, so lets see how our rooms work. Let's go to the third location in the house, enter that room, go through the door we find there and put something in the space we made. Can we guess where it will wind up?
Here's the steps one by one:
use [spot room door next room space][
;-- Third spot in house:
spot: third house
;-- Room is the first thing there
room: first spot
;-- which room is this?
print first room
;-- Door is the third thing in the room
door: third room
;-- The next room is the first thing in the door
next-room: first door
;-- Which room is this?
print first next-room
;-- The space is the second thing in the next-room
space: second next-room
;-- Let's put our kazoo here
insert space 'kazoo
Well, as you should see from what is printed out, the third room in the house is actually room 5. We went through the door and came to room 3. Then we put our kazoo in there. We can verify:
Well, the above step by step method is rather tedious, so let's whip together some handy accessor functions.
foreach [accessor body][
][accessor func [i [integer!]] body]
foreach [accessor body][
][accessor func [blk [block!]] body]
So, now we can do our previous example a little more succinctly. This time we'll go to the sixth place in the house, enter that room, leave something in the space there, go through the door and put something in the space we get to in the next-room.
use [first-room next-room][
insert room-space first-room: house-room 6 'rake
insert room-space next-room: door-room room-door first-room 'saw
room-space first-room "in" room-name first-room newline
room-space next-room "in" room-name next-room
If you experiment, you will find other ways of walking around the rooms. What all this looniness should demonstrate is that you can use blocks to hold the positions of other blocks, including the same block. You can create arbitrarily complex structures with multiple navigation routes.
Jeff Kreis jeff at rebol dot com
Posting to Web Forms Tip
by Graham Chiu
When you write a Rebol script that posts data to a web form, you have to know all the variable names, including the hidden variables. With large complex web pages, it can be rather tedious trying to find all the names, and values.
Luckily there's a very simple solution. There is another script from that era ( http://www.rebol.com/library/html/cgidump.html ) that dumps all the cgi variables to the browser. I modified it to display the form variables when sent by a POST as well as a GET, and saved it to my server as http://www.compkarori.com/cgi-local/cgidump.r
Now, all I need do is to save the form I'm looking at to my hard drive using my browser's 'Save As' function, edit it so that the action of the form now points to my cgidump.r script:
and now when I submit the form, it goes to my server and dumps all the names and values to my browser.
You're welcome to use the cgidump.r script if you wish.
Graham Chiu gchiu at compkarori dot co dot nz
HTTP Tips and Tricks
by Sterling Newton
This short article covers a few of the most commonly asked questions about the HTTP protocol. You're in luck too, the answer to all the questions is yes.
- Can I change the outgoing user-agent field?
- Can I see the reply headers from the web server?
- Can I do a partial download, to resume a broken transfer?
Rebol, by default, announces itself as 'Rebol version' where version is the version number of the Rebol binary that is running. Some sites check this field and compare it to a list, which usually only include Netscape and Internet Explorer to see if the wonderful feature they wish to provide you with is supported. If not, you get an error page which is of no use to you at all. This is why some browsers out there identify themselves as Mozilla (Netscape) compatible.
These sites are fewer in number as time goes on but you might be trying to read one. And, sadly, the site admin does not understand that you are perfectly capable of parsing out the 27K of garbage on the page to get to your 15 byte stock quote without the use of Java, Flash, or audio plugins with Rebol.
And now, the one line solution to your problems:
system/schemes/http/user-agent: "Mozilla/4.5 [en] (compatible)"
or any other string you want. You could change it to "Hey, guy looking at the web logs! Buy Rebol!" if you really wanted to. ;)
Quite often there are situations where you want to know the header information that is returned from the web server for your request. The headers contain differing amounts of information about the web server and the page that s being returned like the server type, the MIME type of the data coming back, and perhaps most important, any cookies that the page is trying to set in your browser.
But when you do a 'read http://www.rebol.com' all you get back is the web page, right? So what do you do? Call the Ghostbusters? I doubt it, though they could use something like Rebol to help track the ghosts they have trapped. Anyway, you need to use 'open' instead of 'read' so that you get a port reference back. The header information is stored in the locals object in the port, in a field called headers.
I know this all sounds complicated when written down in paragraph form, so here we go... the two line solution:
p: open http://www.rebol.com
And there you go. Huh? Oh, "how do you get the web page now?" OK, three lines:
page-data: copy p
Partial Download/Resumed Transfer
Finally something for the adventurous ones of you who are writing those big data-miners and downloading huge files that occasionally have a download timeout or those of you who have to pay by the minute for your web access and don't want to start that download from the beginning again, there are really long sentences like this plus a way to resume downloads in Rebol!
First, pop open a Rebol console and type this:
Do you see it? It's down there towards the bottom... yes! Read/skip is defined to skip a number of bytes before returning the rest. This is exactly what you do with HTTP (or FTP). Here it comes, the one line solution to all your partial download needs:
read/skip http://www.ibm.com 50
Keep in mind that not all server support this type of transfer. In fact, www.rebol.com does not. You should try it on the server before you assume that it will work. IF you want to see what it is doing "behind the scenes", do this:
read/skip http://www.ibm.com 50
and you will see a line in the outgoing headers that looks like:
which tells the server that you want the file starting at byte number 50. If you are joining parts of a download together with this, test out the starting point carefully as an off-by-one error is really easy to make in that type of code.
Well, that's it for now. Happy super-powered websurfing, webmining, and automated webwandering!
Sterling Newton sterling at rebol dot com
ALERT ALERT ALERT
By Colin Sheperton
The simple Rebol/View Alert word has depths not hinted at in its help:
>> Help Alert
Flashes a message to the user. Waits for a user
ALERT is a function value.
str -- (Type: any)
Alert will take a block and use it to present a three-way choice to the user. Try this to test it out:
print alert ["Do you really want to exit?" "Yes" "No" "Cancel"]
Pressing "Yes" returns True, No returns False, and Cancel returns None.
You can use this in code that confirms an exit:
View Layout [Button "Exit"
[ExitChoice: Alert ["How do you want to Exit?"
"Save" "No Save" "Cancel"
If ExitChoice [ Print "Put save code here"
If ExitChoice = False
[ Print "put no-save, tidy-up here"
; "Do nothing for a Cancel"
] ;Button Action
When the user clicks the Exit button, the Alert box offers them three choices: "Save" "No Save" and "Cancel". The Quit statement is commented out in the code example to let you test this out without forever reloading the console.
There doesn't seem to be any way of making the Alert buttons bigger, so you are limited to three pithy texts.
Colin Sheperton Sanghabum at aol dot com
By Garth Rebola
This trick applies to Rebol/Command and View-Pro for Microsoft Windows.
Command the Mouse
Under win32, there are a few functions available which allow you to control the mouse. These functions are found in the system library user32.dll. It's a snap to load them into Rebol/Command or View/Pro. Here's a script which creates routines to set and get the mouse position and generate right and left clicks.
Title: "Mouse control routines"
u32lib: load/library %user32.dll
set-cursor: make routine! [
x [int] y [int] return: [int]
] u32lib "SetCursorPos"
pos: make struct! [x [int] y [int]] none
get-cursor: make routine! [
p [struct! [x [int] y [int]]] return: [int]
] u32lib "GetCursorPos"
mouse-event: make routine! [
flags [int] x [int] y [int]
dwdata [int] dwextra [int]
] u32lib "mouse_event"
left-click: does [
mouse-event 2 pos/x pos/y 0 0
mouse-event 4 pos/x pos/y 0 0
right-click: does [
mouse-event 8 pos/x pos/y 0 0
mouse-event 16 pos/x pos/y 0 0
The routine MOUSE-EVENT is being passed a few magic numbers which come from constants found in the header file winuser.h (or maybe it was windows.h — can't remember).
[Be advised that the MS documentation says some of these functions are deprecated under later versions of windows (tested under windows 98). They are replaced by functions that are a little more complicated which take structs as arguments. Those new functions are still very much doable from Rebol/Command, but that's a topic for a different article. (-:]
Now that we have our mouse under Rebol's control, we can have some fun. Here's some bouncing mouse tricks:
Title: "Bouncing Mouse"
dir-set: func [dr x y /local r dx dy][
dx: dr/x dy: dr/y
foreach check [
[x < 0 dx: 15][x > scr-size/x dx: -15]
[y < 0 dy: 15][y > scr-size/y dy: -15]
to-pair reduce [dx dy]
do bounce: does [
xy: xy + dr
dr: dir-set dr xy/x xy/y
set-cursor xy/x xy/y
The time we pass to wait (0:0:0.001) was just a value that worked well on my machine. You may want to tweak it to suit your tastes.
Okay, now here's something that might be more practical for testing purposes, a mouse motion recorder:
Title: "Mouse record and playback"
play-back: func [evt [block!]][
foreach xy evt [
set-cursor xy/x xy/y
capture: func [/local evt ep start-time][
evt: copy 
ep: open [scheme: 'event]
ep/awake: func [port /local e][
if not e: pick port 1 [return none]
append evt e/offset
do e true
view/new/offset layout [
VH1 "Recording mouse"
button "Done" [
forever [wait ep]
We only are capturing mouse motion events, but we could also capture clicks, and we have the routines to generate them — an exercise for the reader. :-)
POP to mbox Filter
By Jeff K & Tom F
[Built in email conversations with Tom F (balayo at mindspring dot com)]
Let's build a pop to mbox filtering script. What we want is to download our mail from pop, and we want to be able to filter what we get to different mbox formatted files.
Somewhere along the line I'd written a basic pop filter as an answer to someone on the mailing list. From the Rebol script library we took the mbox saving script. A little reorganization and integration and we have the workings of a pretty decent popmail-filter script. This script will zap a fair bit of spam while it's at it as well. If you've ever been frustrated with procmail, you might find this useful.
Below is the script followed by a short discussion of its workings.
Title: "Filter to Mailbox Files"
;--- Settings ----------------------------------------
pass: ask/hide "Password? "
pad: [#"0" ""]
spc: #" "
;--- Our filters
[%frogs.mbox From [firstname.lastname@example.org email@example.com]]
[%alert.mbox Subject ["Oh no!" "Yikes!" "Hey!" "Woops!"]]
[%tests.mbox Subject ["test" "foobar"]]
%lists.mbox From [
[%main.mbox To [firstname.lastname@example.org]]
;--- Make the filter rules
make-rule: func [list [block!] /local rule][
rule: copy 
foreach item list [
repend rule [item '|]
head remove back tail rule
foreach filter filters [
change/only at filter 3 make-rule filter/3
do-filter: func [
mail [object!] "Imported email object"
filter [block!] "One of our filters"
if any [
not field: in mail filter/2
not field: get field
if parse field reduce [filter/3 'to 'end][filter/1]
;-- Results in false otherwise
save-to-mbox: func [
file [file!] "MBox format file"
mail [object!] "Import-email object"
mesg [string!] "Raw email message"
write/append file rejoin [
"From " mail/from spc
pick days when/weekday spc
pick months when/month spc
pick pad when/day < 10 when/day spc
pick pad when/time < 10:00 when/time spc
newline mesg newline
process-mail: does [
forall pop [
mail: import-email mesg: first pop
prin [index? pop mail/from tab mail/subject "->"]
;-- Paranoid? backup mail here just in case
; write/append %mail-backup.dat rejoin [mesg "^/^/"]
if not foreach filter filters [
if target: do-filter mail filter [
save-to-mbox target mail mesg
][save-to-mbox spam-box mail mesg]
either remove-mail [remove pop][inbox: next pop]
;--- and go ...
pop: open to-url rejoin ["pop://" me ":" pass "@" server]
First the settings are placed at the top for customization. One of our settings prevents the mail from being removed from the server as a preventative measure. When ever we hack our mail tools, we must always abide by the cardinal first rule of mail-hacking: NEVER LOSE ANY MAIL!
If a subtle bug throws away some of our mail, we'll never know what great opportunity we might have missed. :-) When we are confident the filter works, then we can remove the mail. (Do a few of test runs, at least, sending yourself different emails). Even after you've turned on removal of pop mails, you may consider uncommenting the back up code under the comment ";—Paranoid? .. " Mails are funny things. Some mail could show up a week from now that is oddly formatted which brings out a bug in your filter. In that unlikely event there would still be a raw copy of the mail with the paranoid backup measure. After the filter has worked flawlessly for a month you might remove the paranoia line and the large backup mail file. You can't be too safe when it comes to your mail!
So, we build our filters in the following simple format:
[%file-to-store field-to-check ["things" "to" "check" "for"]]
We can filter on Subject, From, To, Content, etc... In our process-mail function, each imported mail object is checked against our filters.
To determine if a filter matches, we use a simple PARSE trick, building a rule out of the search block in our filter. The above filter is turned into a parse rule that looks like this:
["things" | "to" | "check" | "for"]
We simultaneously verify and get the field we're interested in from the mail object:
if any [
not field: in mail filter/2
not field: get field
Once we have the field, we check for a match using our simple rule. Given the previous example, the PARSE ends up looking like this:
parse field [["things" | "to" | "check" | "for"] to end]
Remember that the default behavior for PARSE when encountering a block of alternatives is to try and move through the different items. If none of the alternatives are found in the field the PARSE fails. Otherwise, the PARSE runs to the end and is true, which, in this case, means the file to save to is returned.
As soon as one of our filters matches we save the message to the appropriate mailbox and move on. We can create implicit priorities in the order of our filters, so that mails that might match more than one filter will go in the most appropriate place first.
Notice that the last filter we have catches mails that are actually sent to you (email@example.com). This means that everything else that hasn't matched (but which is specifically addressed to you) will go into your %main.mbox file. This is important because a good deal of spam is not explicitly addressed to you. Those spam mails will not match on the last filter, and where will the go?
Here's the filter loop:
if not foreach filter filters [
][save-to-mbox spam-box mail mesg]
If you go through all the filters and nothing matches, the final result of the foreach loop will be false (successful filters cause a BREAK/RETURN TRUE). No filters means save to the spam-box. Bye bye spam.
Why keep a spam-box? You may feel like perusing it for laughs at some time, happy to have canned their pernicious spam with the handy aid of Rebol and a short and simple script.
Jeff K jeff at rebol dot com
Tom F balayo at mindspring dot com
A Day in the Life of Rebol
A Day in the Life of Rebol
Thanks Jeff for starting the Rebol/Zine... We, the community of Rebolers, really need something like this. A year from now, after the zine has transformed into Rebol/World magazine with 50,000 subscribers, I think we will all wonder why it took so long for someone to start it.
It's great to see Rebol springing back to life again in so many places and for so many uses. Our .com web site is going crazy, so is the email list, and from the web stats, I see that many of our site hits are new users — judging from the intro types of pages that are being viewed.
So, I would say that the Zine premier has perfect timing.
Mornings at the Ranch
I live the Rebol life. I'm not just talking about creating the Rebol vision but in nearly all the tasks I do each day. Rebol keeps Rebol alive. Literally. Without it, I would be lost.
As the sun comes up over the ranch, I jump from bed and dive head-first into Rebol. I really enjoy the instant access, on-line community that Rebol is building. With Rebol distributed across the globe, something is always happening, something new each day. So, after morning greetings to Cindy and the kids, I pop open Link, View, and RIM.
That's an interesting combination: Link, View, and RIM. Link is the heart; View is the collection; and, RIM is the "now". Funny how these all have their own purposes. It's also funny how they all feel so different.
With Link I take a quick glance at History to see what new docs or scripts have appeared or been edited during the night. I then catch up with the latest discussions on the developer conference. It works so amazingly well for a 10 K app.
My morning dose of Rebol/Link I suppose is most peoples' equivalent of coffee and the newspaper. But, it's more than just a wakeup ritual for me. I pop into Link hundreds of times a day - for nearly everything I do. It's where I live, and I suppose I'm its biggest user too.
Among other things, Link has satisfied a wonderful goal in my life. It has totally eliminated the briefcase and disks that I used to carry back and forth to the office. Sure, I realize that there are other methods to do that, like VPN, but I just like the ease of Link. I see exactly the same folders and files on all of the five machines that I use during the day. If I need it on a new machine, it's installed and ready in seconds. I know, I know, Bill does it too or will do it soon. The Amiga all over again, eh? Could you yank that arrow out of my back. Thanks.
Next comes View. For me view represents the library or archive. It's the source. Between the Script Library and the Reb, there are now hundreds of interesting Rebol apps and scripts. I enjoy dropping into the Reb and seeing what amazing demo someone has come up with. It again reminds me of the Amiga, and the demo contests that blew each other away. Expression. Creativity. Did I ever tell you that I started out in TV before computers?
Then, it's on to that cute little 6Ker, RIM. I check-in to say hi to Ryan who always seems to be there, and I see what 10 new RIM relay and other features he added during the night. Who else might be around.... Perhaps a newbie who's just tried the Rebol browser plugin. Fun to say hi. Fun to know I'm communicating using a 6K app. It's a lot like working someone in Russia with a 1 watt Ham radio rig. Yep, KB6ZST, in case you're one too. (And the Internet boys thought they developed asynchronous networking. Grin.)
Now it's time for some real work. Time to concentrate on an important document, spreadsheet, or script. Maybe write another How-To doc for the programmers out there. Always from the Link desktop. Of course.
Then, time to head to the office... completely free from papers, docs, and all that other baggage... and knowing that it will all be there in the office ready to resume when I arrive.
Such a minor thing. What a major benefit.
Days at Headquarters
Ok, so I hit the office ready to work. Pop open email. Oh, man! The spam wave covers me and pushes me under. I fight to get back to the surface for some air.
Email is becoming useless. I use nearly 30 well-tuned Eudora filters to deal with my email, but modern spam finds a way through it all. Reminds me of Jurassic park and that "Nature will find a way." I could have written that. An hour goes by picking through the pile. A lot of it is junk. What a waste.
The web site needs some updating, so I turn again to Link to open the source files that create the Rebol.com site. Now there's more than syncing going on. A little Rebol script rebuilds the site for me and posts the new pages back to our Atlanta server. That little script can rebuild the *entire* site in about one second. Who said scripting needs to be slow.
Another script sweeps the site and finds bad links. Yet another script analyzes the web logs to discover what interesting patterns have emerged. All of these scripts combined would amount to fewer bytes than a single Amazon web page... but they "work" the net for me and save valuable time. Sure, I've tried a dozen web tools from just about everyone. Nope, they didn't cut it for me. Wasted too much of my time. Don't send me an email. I spent years trying them all.
Maybe today is one of those days when we need to build a new Rebol version. Link gives me access to Jeff's amazing "box builder" app that, with the single click of my mouse, fires up the "build farm" and compiles, links, tests, zips, and posts Rebol for 42 platforms. Whoops, look at that... it's reporting that one of the tests failed on NetBSD for the Dec Alpha. Couldn't be done without Rebol/Command.
So, time to announce the new release. That means it's time to fire up the super-rebodex, the custom modified holy grail of PR. It not only keeps track of more than 300 press contacts, but, lets me quickly customize the email that I sent to each of them. Logs the date and time of each contact too.
And now it's time to hammer out a quick spec or doc. Make-spec and Make-doc let me so faster than any word processor or html editor that you will ever find. Let's me focus on the words.
And then there's our credit card processing, and RAMBO our Rebol bug database, and our Rebol helpdesk... and so it goes. Hundreds of Rebol scripts working together to magnify the work of very few into the work of hundreds.
Curious as to how many scripts I actually have, I search my harddisk for *.r and the MS search program stops at 10,000 Rebol scripts. I guess Bill decided that's all I'm allowed to have.
But, it's ok. I head home, no briefcase in hand... ready to pick up where I left off with a few late night scripts, docs, and chats. Sometimes the Internet feels like one big computer and as if Rebol has found a way to harness it like some kind of meta-OS. It's like generating power from sea waves. You script the net like it's your own personal OS.
That's the future if you ask me. Bill wants to do it too. 100 MB of .net power... and that just opens the first window. Amazing what you can do with billions of dollars, isn't it?
See you around in Rebolspace... or catch you on RIM.
-Carl carl at rebol dot com