REBOL/Zine Volume 1 Issue 4 Date: 3-Aug-2001 Issues: prev | next ===Lit-Word! 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! -Jeff Kreis ===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: REBOL [ print ["I was loaded" now/time/precise] Title: random "XYZ123" Date: now/date set 'headr self ] 1 2 3 Try: probe headr 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 "foo" == foo load/all "foo" == [foo] 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 "^/^/" mark: (remove/part mark 2 mark: insert mark "
^/^/
") :mark skip]] 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! ---Introduction 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. ---Blurb 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. ---Code REBOL [ 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 regions: [ [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] ] return none ] if oset > length? regions [return none] iter-face/offset: regions/:oset/1 iter-face/size: regions/:oset/2 iter-face/text: iter-face/color: iter-face/image: iter-face/effect: iter-face/action: none iter-face/feel: make face/feel [] iter-face/font: make bfont [] iter-face/edge: make bedge [] switch oset [ 1 [ iter-face/font/colors: none iter-face/text: "It's just one face!" iter-face/font/size: 24 iter-face/edge: make edge [size: none effect: none] ] 2 [ 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 ] 3 [ iter-face/image: logo iter-face/effect: [fit colorize 195.0.0] iter-face/text: none iter-face/edge: make edge [size: none effect: none] ] 4 [ iter-face/effect: [cross] iter-face/edge: make get in get-style 'check 'edge [] iter-face/color: white iter-face/feel: get in get-style 'check 'feel ] 5 [ iter-face/text: "<--- Look! A check box." iter-face/edge: none ] 6 [ iter-face/feel: bfeel iter-face/color: white iter-face/effect: [fit arrow] iter-face/action: [ regions/3/1: regions/3/1 - 0x5 show myface ] ] 7 [ iter-face/feel: bfeel iter-face/color: white iter-face/effect: [fit arrow rotate 180] iter-face/action: [ regions/3/1: regions/3/1 + 0x5 show myface ] ] 8 [] ; this is here because is hides another ; weird behavior of the whole button problem ] iter-face ] ] ] ---Conclusion 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..." +Sterling Newton ===RUGBY ---LDC 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 Rebol. Enter Lightweight Distributed Computing (LDC). Enter Rugby. 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) ---Rugby Intro 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! ---Serving. 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. ---The View. 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. rugby-view some-layout 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 ===It's Alive!!! 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