From a557f1a3d4b6da2281a611aaa7112abdd1be82d7 Mon Sep 17 00:00:00 2001 From: Greg Borenstein Date: Tue, 24 Jun 2008 13:48:25 -0700 Subject: [PATCH] moving over from svn --- History.txt | 75 + License.txt | 282 +++ Manifest.txt | 32 + README.txt | 43 + Rakefile | 139 ++ bin/rad | 142 ++ lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp | 250 +++ lib/libraries/SWSerLCDpa/SWSerLCDpa.h | 62 + lib/libraries/SWSerLCDpa/SWSerLCDpa.o | Bin 0 -> 4140 bytes .../examples/K107_224_Demo/K107_224_Demo.pde | 83 + .../examples/K107_240Demo/K107_240Demo.pde | 86 + .../examples/K107_LCD_Plot/K107_LCD_Plot.pde | 152 ++ .../LCDPHA_216_Demo/LCDPHA_216_Demo.pde | 97 + .../LCDPHA_420Demo/LCDPHA_420Demo.pde | 113 ++ .../examples/LCD_Plot224/LCD_Plot224.pde | 153 ++ lib/libraries/SWSerLCDpa/keywords.txt | 18 + lib/rad.rb | 5 + lib/rad/antiquated_todo.txt | 47 + lib/rad/arduino_sketch.rb | 533 ++++++ lib/rad/generators/makefile/makefile.erb | 243 +++ lib/rad/generators/makefile/makefile.rb | 39 + lib/rad/init.rb | 12 + lib/rad/sim/arduino_sketch.rb | 57 + lib/rad/sim/arduino_sketch.rb~ | 10 + lib/rad/tasks/build_and_make.rake | 88 + lib/rad/tasks/rad.rb | 2 + lib/rad/todo.txt | 13 + lib/rad/version.rb | 9 + pkg/rad-0.2.2.gem | Bin 0 -> 48128 bytes pkg/rad-0.2.2.tgz | Bin 0 -> 47860 bytes pkg/rad-0.2.2/History.txt | 75 + pkg/rad-0.2.2/License.txt | 282 +++ pkg/rad-0.2.2/Manifest.txt | 32 + pkg/rad-0.2.2/README.txt | 43 + pkg/rad-0.2.2/Rakefile | 139 ++ pkg/rad-0.2.2/bin/rad | 142 ++ .../lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp | 250 +++ .../lib/libraries/SWSerLCDpa/SWSerLCDpa.h | 62 + .../lib/libraries/SWSerLCDpa/keywords.txt | 18 + pkg/rad-0.2.2/lib/rad.rb | 5 + pkg/rad-0.2.2/lib/rad/arduino_sketch.rb | 533 ++++++ .../lib/rad/generators/makefile/makefile.erb | 243 +++ .../lib/rad/generators/makefile/makefile.rb | 39 + pkg/rad-0.2.2/lib/rad/init.rb | 12 + .../lib/rad/tasks/build_and_make.rake | 88 + pkg/rad-0.2.2/lib/rad/tasks/rad.rb | 2 + pkg/rad-0.2.2/lib/rad/todo.txt | 13 + pkg/rad-0.2.2/lib/rad/version.rb | 9 + pkg/rad-0.2.2/scripts/txt2html | 67 + pkg/rad-0.2.2/setup.rb | 1585 +++++++++++++++++ .../spec/models/arduino_sketch_spec.rb | 82 + pkg/rad-0.2.2/spec/models/spec_helper.rb | 2 + pkg/rad-0.2.2/spec/spec.opts | 1 + .../website/examples/assembler_test.rb.html | 73 + .../website/examples/gps_reader.rb.html | 39 + .../website/examples/hello_world.rb.html | 38 + .../website/examples/serial_motor.rb.html | 41 + pkg/rad-0.2.2/website/index.html | 177 ++ pkg/rad-0.2.2/website/index.txt | 64 + .../javascripts/rounded_corners_lite.inc.js | 285 +++ pkg/rad-0.2.2/website/stylesheets/screen.css | 169 ++ pkg/rad-0.2.2/website/template.rhtml | 48 + scripts/txt2html | 67 + setup.rb | 1585 +++++++++++++++++ spec/examples/hello_world.rb | 11 + spec/examples/hello_world.rb~ | 8 + spec/examples/serial_motor.rb | 12 + spec/models/arduino_sketch_spec.rb | 82 + spec/models/spec_helper.rb | 2 + spec/sim/hello_world_spec.rb | 42 + spec/sim/hello_world_spec.rb~ | 8 + spec/spec.opts | 1 + website/examples/assembler_test.rb.html | 73 + website/examples/gps_reader.rb.html | 39 + website/examples/hello_world.rb.html | 38 + website/examples/serial_motor.rb.html | 41 + website/index.html | 177 ++ website/index.txt | 64 + .../javascripts/rounded_corners_lite.inc.js | 285 +++ website/stylesheets/code.css | 17 + website/stylesheets/screen.css | 169 ++ website/template.rhtml | 48 + 82 files changed, 10212 insertions(+) create mode 100644 History.txt create mode 100644 License.txt create mode 100644 Manifest.txt create mode 100644 README.txt create mode 100644 Rakefile create mode 100644 bin/rad create mode 100755 lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp create mode 100755 lib/libraries/SWSerLCDpa/SWSerLCDpa.h create mode 100644 lib/libraries/SWSerLCDpa/SWSerLCDpa.o create mode 100644 lib/libraries/SWSerLCDpa/examples/K107_224_Demo/K107_224_Demo.pde create mode 100644 lib/libraries/SWSerLCDpa/examples/K107_240Demo/K107_240Demo.pde create mode 100644 lib/libraries/SWSerLCDpa/examples/K107_LCD_Plot/K107_LCD_Plot.pde create mode 100644 lib/libraries/SWSerLCDpa/examples/LCDPHA_216_Demo/LCDPHA_216_Demo.pde create mode 100644 lib/libraries/SWSerLCDpa/examples/LCDPHA_420Demo/LCDPHA_420Demo.pde create mode 100644 lib/libraries/SWSerLCDpa/examples/LCD_Plot224/LCD_Plot224.pde create mode 100644 lib/libraries/SWSerLCDpa/keywords.txt create mode 100644 lib/rad.rb create mode 100644 lib/rad/antiquated_todo.txt create mode 100644 lib/rad/arduino_sketch.rb create mode 100644 lib/rad/generators/makefile/makefile.erb create mode 100644 lib/rad/generators/makefile/makefile.rb create mode 100644 lib/rad/init.rb create mode 100644 lib/rad/sim/arduino_sketch.rb create mode 100644 lib/rad/sim/arduino_sketch.rb~ create mode 100644 lib/rad/tasks/build_and_make.rake create mode 100644 lib/rad/tasks/rad.rb create mode 100644 lib/rad/todo.txt create mode 100644 lib/rad/version.rb create mode 100644 pkg/rad-0.2.2.gem create mode 100644 pkg/rad-0.2.2.tgz create mode 100644 pkg/rad-0.2.2/History.txt create mode 100644 pkg/rad-0.2.2/License.txt create mode 100644 pkg/rad-0.2.2/Manifest.txt create mode 100644 pkg/rad-0.2.2/README.txt create mode 100644 pkg/rad-0.2.2/Rakefile create mode 100644 pkg/rad-0.2.2/bin/rad create mode 100755 pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp create mode 100755 pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/SWSerLCDpa.h create mode 100644 pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/keywords.txt create mode 100644 pkg/rad-0.2.2/lib/rad.rb create mode 100644 pkg/rad-0.2.2/lib/rad/arduino_sketch.rb create mode 100644 pkg/rad-0.2.2/lib/rad/generators/makefile/makefile.erb create mode 100644 pkg/rad-0.2.2/lib/rad/generators/makefile/makefile.rb create mode 100644 pkg/rad-0.2.2/lib/rad/init.rb create mode 100644 pkg/rad-0.2.2/lib/rad/tasks/build_and_make.rake create mode 100644 pkg/rad-0.2.2/lib/rad/tasks/rad.rb create mode 100644 pkg/rad-0.2.2/lib/rad/todo.txt create mode 100644 pkg/rad-0.2.2/lib/rad/version.rb create mode 100644 pkg/rad-0.2.2/scripts/txt2html create mode 100644 pkg/rad-0.2.2/setup.rb create mode 100644 pkg/rad-0.2.2/spec/models/arduino_sketch_spec.rb create mode 100644 pkg/rad-0.2.2/spec/models/spec_helper.rb create mode 100644 pkg/rad-0.2.2/spec/spec.opts create mode 100644 pkg/rad-0.2.2/website/examples/assembler_test.rb.html create mode 100644 pkg/rad-0.2.2/website/examples/gps_reader.rb.html create mode 100644 pkg/rad-0.2.2/website/examples/hello_world.rb.html create mode 100644 pkg/rad-0.2.2/website/examples/serial_motor.rb.html create mode 100644 pkg/rad-0.2.2/website/index.html create mode 100644 pkg/rad-0.2.2/website/index.txt create mode 100644 pkg/rad-0.2.2/website/javascripts/rounded_corners_lite.inc.js create mode 100644 pkg/rad-0.2.2/website/stylesheets/screen.css create mode 100644 pkg/rad-0.2.2/website/template.rhtml create mode 100644 scripts/txt2html create mode 100644 setup.rb create mode 100644 spec/examples/hello_world.rb create mode 100644 spec/examples/hello_world.rb~ create mode 100644 spec/examples/serial_motor.rb create mode 100644 spec/models/arduino_sketch_spec.rb create mode 100644 spec/models/spec_helper.rb create mode 100644 spec/sim/hello_world_spec.rb create mode 100644 spec/sim/hello_world_spec.rb~ create mode 100644 spec/spec.opts create mode 100644 website/examples/assembler_test.rb.html create mode 100644 website/examples/gps_reader.rb.html create mode 100644 website/examples/hello_world.rb.html create mode 100644 website/examples/serial_motor.rb.html create mode 100644 website/index.html create mode 100644 website/index.txt create mode 100644 website/javascripts/rounded_corners_lite.inc.js create mode 100644 website/stylesheets/code.css create mode 100644 website/stylesheets/screen.css create mode 100644 website/template.rhtml diff --git a/History.txt b/History.txt new file mode 100644 index 0000000..a74a873 --- /dev/null +++ b/History.txt @@ -0,0 +1,75 @@ +== 0.2.2 2008-04-27 +* 2ish updates: + - updated makefile template and cli to expect arduino-0011 + - applied David Michael's patch for longs + +== 0.2.1 2008-04-02 +* 6ish updates: + - applied Brian Riley's SWSerLCDpa patch + - experimental libraries system in vendor/libraries + - fixed require 'yaml' bug in makefile.rb + - enhancements to the rad command: can set project config. + - added first significant documentation + - added examples directory to the website + +== 0.2.0 2008-03-15 +* 8ish updates, some major: + * fixed regular serial support + * class method for writing functions in assembler + * applied Scott Windsor's patch for software serial support + * added support for HIGH/LOW/ON/OFF constants + * add an option for skipping the reset prompt on rake make:upload + * changed default arduino location to be more realistic + * put screencasts #1 and #2 on rad.rubyforge.org + * put google analytics on rad.rubyforge.org + +== 0.1.1 2007-10-29 +* 2 major fixes: + * explicitly specify path to arduino avr tools (this was broken on systems that didn't have the avr toolchain installed, oops) + * add explicit path to avrdude.conf in the upload make target + +== 0.1.0 2007-10-28 +* 4ish major updates: + * Arduino interop has been updated, and consequently now requires Arduino 0010: + * new Makefile imported, with new configuration items (see below) + * main() function added to C++ output + * make:upload rake task has been updated + * C++ generation has been changed to produce a more readable output + * configuration file changes: + * hardware + * fixed typo in serial_port key + * added mcu key to specify atmega8/atmega168. Defaults to atmega168 + * changed serial_port to /dev/tty.usbserial*, which will pick the first device that matches that blob + * software + * updated arduino_root for 0010 +* 2 major enhancement: + * gem should now correctly install RubyToC + * can now enable internal pull-up resistors on input pins by passing the :pullup => true parameter to input_pin +* 2 minor enhancements: + * cleanups in makefile.rb + * serial_print_str method added; can now send strings over the serial port + +== 0.0.4 2007-07-24 + +* 1 major enhancement: + * serial_begin class method + +== 0.0.3 2007-07-23 + +* 1 major enhancement: + * bug fix in blink helper method + +== 0.0.2 2007-07-23 + +* 1 major enhancement: + * blink helper method + +== 0.0.1 2007-07-22 + +* 1 major enhancement: + * Initial release + * most features of the Arduino software library functional + * experimental implementation of the serial interface + * complete rake-based build, compile, and upload cycle + + diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..8604702 --- /dev/null +++ b/License.txt @@ -0,0 +1,282 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + + diff --git a/Manifest.txt b/Manifest.txt new file mode 100644 index 0000000..56beafc --- /dev/null +++ b/Manifest.txt @@ -0,0 +1,32 @@ +History.txt +License.txt +Manifest.txt +README.txt +Rakefile +bin/rad +lib/libraries/SWSerLCDpa/keywords.txt +lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp +lib/libraries/SWSerLCDpa/SWSerLCDpa.h +lib/rad.rb +lib/rad/arduino_sketch.rb +lib/rad/init.rb +lib/rad/todo.txt +lib/rad/tasks/build_and_make.rake +lib/rad/tasks/rad.rb +lib/rad/version.rb +lib/rad/generators/makefile/makefile.erb +lib/rad/generators/makefile/makefile.rb +scripts/txt2html +setup.rb +spec/models/spec_helper.rb +spec/models/arduino_sketch_spec.rb +spec/spec.opts +website/index.html +website/index.txt +website/javascripts/rounded_corners_lite.inc.js +website/stylesheets/screen.css +website/template.rhtml +website/examples/assembler_test.rb.html +website/examples/gps_reader.rb.html +website/examples/hello_world.rb.html +website/examples/serial_motor.rb.html diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..ff55fab --- /dev/null +++ b/README.txt @@ -0,0 +1,43 @@ +=Welcome to RAD (Ruby Arduino Development) + +RAD is a framework for programming the Arduino physcial computing platform using Ruby. RAD converts Ruby scripts written using a set of Rails-like conventions and helpers into C source code which can be compiled and run on the Arduino microcontroller. It also provides a set of Rake tasks for automating the compilation and upload process. + +For a full introduction see http://rad.rubyforge.org + +==Documentation + +The main documentation is here: ArduinoSketch. + +See also the Arduino Software reference: http://www.arduino.cc/en/Reference/HomePage + +==Examples + +See the examples directory for lots of examples of RAD in action: http://rad.rubyforge.org/examples + +==Getting Started + +Install the gem: + +$ sudo gem install rad + +Run the rad command to create a new project: + +$ rad my_project + +Write a sketch that will blink a single LED every 500ms: + + class MyProject < ArduinoSketch + output_pin 13, :as => led + + def loop + blink led, 500 + end + end + +Attach your Arduino and use rake to complile and upload your sketch: + +$ rake make:upload + +==Get Involved + +Cheers? Jeers? Wanna help out? Contact Greg Borenstein: greg [dot] borenstein [at] gmail [dot] com \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..00e65bb --- /dev/null +++ b/Rakefile @@ -0,0 +1,139 @@ +require 'rubygems' +require 'rake' +require 'rake/clean' +require 'rake/testtask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/rdoctask' +require 'rake/contrib/rubyforgepublisher' +require 'fileutils' +require 'hoe' +begin + require 'spec/rake/spectask' +rescue LoadError + puts 'To use rspec for testing you must install rspec gem:' + puts '$ sudo gem install rspec' + exit +end + +include FileUtils +require File.join(File.dirname(__FILE__), 'lib', 'rad', 'version') + +BIN = "rad" +AUTHOR = 'Greg Borenstein' # can also be an array of Authors +EMAIL = "greg@mfdz.com" +DESCRIPTION = "A framework for programming the Arduino physcial computing platform using Ruby. RAD converts Ruby scripts written using a set of Rails-like conventions and helpers into C source code which can be compiled and run on the Arduino microcontroller." +GEM_NAME = 'rad' # what ppl will type to install your gem + +@config_file = "~/.rubyforge/user-config.yml" +@config = nil +def rubyforge_username + unless @config + begin + @config = YAML.load(File.read(File.expand_path(@config_file))) + rescue + puts <<-EOS +ERROR: No rubyforge config file found: #{@config_file}" +Run 'rubyforge setup' to prepare your env for access to Rubyforge + - See http://newgem.rubyforge.org/rubyforge.html for more details + EOS + exit + end + end + @rubyforge_username ||= @config["username"] +end + +RUBYFORGE_PROJECT = 'rad' # The unix name for your project +HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org" +DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}" + +NAME = "rad" +REV = nil +# UNCOMMENT IF REQUIRED: +# REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil +VERS = Rad::VERSION::STRING + (REV ? ".#{REV}" : "") +CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] +RDOC_OPTS = ['--quiet', '--title', 'rad documentation', + "--opname", "index.html", + "--line-numbers", + "--main", "README", + "--inline-source"] + +class Hoe + def extra_deps + @extra_deps.reject { |x| Array(x).first == 'hoe' } + end +end + +# Generate all the Rake tasks +# Run 'rake -T' to see list of generated tasks (from gem root directory) +hoe = Hoe.new(GEM_NAME, VERS) do |p| + p.author = AUTHOR + p.description = DESCRIPTION + p.email = EMAIL + p.summary = DESCRIPTION + p.url = HOMEPATH + p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT + p.test_globs = ["test/**/test_*.rb"] + p.clean_globs |= CLEAN #An array of file patterns to delete on clean. + + # == Optional + p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n") + p.extra_deps = [ ['RubyToC', '>= 1.0.0'] ] + #p.spec_extras = {} # A hash of extra values to set in the gemspec. +end + +CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n") +PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}" +hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc') + +desc 'Generate website files' +task :website_generate do + Dir['website/**/*.txt'].each do |txt| + sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} } + end +end + +desc 'Upload website files to rubyforge' +task :website_upload do + host = "#{rubyforge_username}@rubyforge.org" + remote_dir = "/var/www/gforge-projects/#{PATH}/" + local_dir = 'website' + sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}} +end + +desc 'Generate and upload website files' +task :website => [:website_generate, :website_upload, :publish_docs] + +desc 'Release the website and new gem version' +task :deploy => [:check_version, :website, :release] do + puts "Remember to create SVN tag:" + puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " + + "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} " + puts "Suggested comment:" + puts "Tagging release #{CHANGES}" +end + +desc 'Runs tasks website_generate and install_gem as a local deployment of the gem' +task :local_deploy => [:website_generate, :install_gem] + +task :check_version do + unless ENV['VERSION'] + puts 'Must pass a VERSION=x.y.z release version' + exit + end + unless ENV['VERSION'] == VERS + puts "Please update your version.rb to match the release version, currently #{VERS}" + exit + end +end + +desc "Run the specs under spec/models" +Spec::Rake::SpecTask.new do |t| + t.spec_opts = ['--options', "spec/spec.opts"] + t.spec_files = FileList['spec/models/*_spec.rb'] +end + +desc "Default task is to run specs" +task :default => :spec + diff --git a/bin/rad b/bin/rad new file mode 100644 index 0000000..d44ba3c --- /dev/null +++ b/bin/rad @@ -0,0 +1,142 @@ +#!/usr/bin/env ruby + +begin + require 'rubygems' +rescue LoadError + # no rubygems to load, so we fail silently +end + +require 'optparse' +require 'fileutils' +require 'yaml' + + + +class OptionParser #:nodoc: + + def self.parse(args) + # defaults + + options = {"hardware" => { + "serial_port" => '/dev/tty.usbserial*', + "mcu" => "atmega168", + "physical_reset" => false + }, + "software" => { + "arduino_root" => "/Applications/arduino-0011" + } + } + + opts = OptionParser.new do |opts| + + opts.banner = <<-BANNER +Build a directory for your RAD Project and install RAD in its vendor sub-directory. + +Usage: #{File.basename($0)} + BANNER + + opts.on("-p", "--port [SERIAL PORT]", + "Path to your serial port", + " (default: #{options['hardware']['serial_port']})") do |port| + + options["hardware"]["serial_port"] = port if port + end + + opts.on("-m", "--mcu [PROCESSOR TYPE]", + "Type of processor on your board", + " (default: #{options['hardware']['mcu']})") do |port| + + options["hardware"]["serial_port"] = port if port + end + + opts.on("-r", "--reset [RESET REQUIRED]", + "Require a hardware reset before uploading?", + " (default: #{options['hardware']['physical_reset']})") do |no_reset| + options["hardware"]["physical_reset"] = true unless no_reset + end + + opts.on("-a", "--arduino [ARDUINO DIR]", + "Path to your Arduino install", + " (default: #{options['software']['arduino_root']})") do |arduino| + options["software"]["arduino_root"] = arduino if arduino + end + + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end + end + + opts.parse!(args) + [options, opts] + end + +end + + +# home = ENV['HOME'] || ENV['USERPROFILE'] || ENV['HOMEPATH'] +# begin +# config = YAML::load open(home + "/.rad") +# rescue +# config = false +# end +# + + +# Handle options: +options, parser = OptionParser.parse(ARGV) +sketch_name = ARGV[0] +parser.parse!(["-h"]) unless sketch_name + + +# Build vendor/rad: + +FileUtils.mkdir_p "#{sketch_name}/vendor/rad" +puts "Successfully created your sketch directory." + +FileUtils.cp_r "#{File.dirname(__FILE__)}/../lib/rad/.", "#{sketch_name}/vendor/rad" +puts "Installed RAD into #{sketch_name}/vendor/rad" +puts + +# Build vendor/libraries: + +FileUtils.mkdir_p "#{sketch_name}/vendor/libraries" +puts "Successfully created your libraries directory." + +FileUtils.cp_r "#{File.dirname(__FILE__)}/../lib/libraries/SWSerLCDpa/.", "#{sketch_name}/vendor/libraries/SWSerLCDpa" +puts "Installed SWSerLCDpa into #{sketch_name}/vendor/libraries" +puts + +# Build sketch files, etc.: + +FileUtils.touch "#{sketch_name}/#{sketch_name}.rb" +File.open("#{sketch_name}/#{sketch_name}.rb", "w") do |file| + file << <<-EOS +class #{sketch_name.split("_").collect{|c| c.capitalize}.join("")} < ArduinoSketch +end + EOS +end +puts "Added #{sketch_name}/#{sketch_name}.rb" + +File.open("#{sketch_name}/Rakefile", 'w') do |file| + file << <<-EOS +require 'vendor/rad/init.rb' + EOS +end +puts "Added #{sketch_name}/Rakefile" + +FileUtils.mkdir_p "#{sketch_name}/config" +puts "Added #{sketch_name}/config" + +File.open("#{sketch_name}/config/hardware.yml", 'w') do |file| + file << options["hardware"].to_yaml +end +puts "Added #{sketch_name}/config/hardware.yml" + +File.open("#{sketch_name}/config/software.yml", 'w') do |file| + file << options["software"].to_yaml +end +puts "Added #{sketch_name}/config/software.yml" + +puts +puts "Run 'rake -T' inside your sketch dir to learn how to compile and upload it." \ No newline at end of file diff --git a/lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp b/lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp new file mode 100755 index 0000000..0610de9 --- /dev/null +++ b/lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp @@ -0,0 +1,250 @@ +/* + SWSerLCDpa.cpp - Software serial to Peter Anderson controller chip based + LCD display library Adapted from SoftwareSerial.cpp (c) 2006 David A. Mellis + by Brian B. Riley, Underhill Center, Vermont, USA, July 2007 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/****************************************************************************** + * Includes + ******************************************************************************/ + +#include "WConstants.h" +#include "SWSerLCDpa.h" + +/****************************************************************************** + * Definitions + ******************************************************************************/ + +/****************************************************************************** + * Constructors + ******************************************************************************/ + +SWSerLCDpa::SWSerLCDpa(uint8_t transmitPin) +{ + _transmitPin = transmitPin; + _baudRate = 0; +} + +/****************************************************************************** + * User API + ******************************************************************************/ + +void SWSerLCDpa::begin(long speed) +{ + _baudRate = speed; + _bitPeriod = 1000000 / _baudRate; + + digitalWrite(_transmitPin, HIGH); + delayMicroseconds( _bitPeriod); // if we were low this establishes the end +} + +void SWSerLCDpa::print(uint8_t b) +{ + if (_baudRate == 0) + return; + + int bitDelay = _bitPeriod - clockCyclesToMicroseconds(50); // a digitalWrite is about 50 cycles + byte mask; + + digitalWrite(_transmitPin, LOW); + delayMicroseconds(bitDelay); + + for (mask = 0x01; mask; mask <<= 1) { + if (b & mask){ // choose bit + digitalWrite(_transmitPin,HIGH); // send 1 + } + else{ + digitalWrite(_transmitPin,LOW); // send 1 + } + delayMicroseconds(bitDelay); + } + + digitalWrite(_transmitPin, HIGH); + delayMicroseconds(bitDelay); +} + +void SWSerLCDpa::print(const char *s) +{ + while (*s) { + print(*s++); + delay(1); + } +} + +void SWSerLCDpa::print(char c) +{ + print((uint8_t) c); +} + +void SWSerLCDpa::print(int n) +{ + print((long) n); +} + +void SWSerLCDpa::print(unsigned int n) +{ + print((unsigned long) n); +} + +void SWSerLCDpa::print(long n) +{ + if (n < 0) { + print('-'); + n = -n; + } + printNumber(n, 10); +} + +void SWSerLCDpa::print(unsigned long n) +{ + printNumber(n, 10); +} + +void SWSerLCDpa::print(long n, int base) +{ + if (base == 0) + print((char) n); + else if (base == 10) + print(n); + else + printNumber(n, base); +} + +// -------- PHA unique codes ------------------------- +void SWSerLCDpa::println(void) +{ + print("?n"); + delay(10); +} + +void SWSerLCDpa::clearscr(void) +{ + print("?f"); + delay(100); +} + +void SWSerLCDpa::home(void) +{ + print("?a"); + delay(10); +} + + +void SWSerLCDpa::setgeo(int geometry) +{ + print("?G"); + print(geometry); + delay(200); +} + +void SWSerLCDpa::setintensity(int intensity) +{ + print("?B"); + if (intensity < 16) + print('0'); + print(intensity, 16); + delay(200); +} + +void SWSerLCDpa::intoBignum(void) +{ + print("?>3"); +} + +void SWSerLCDpa::outofBignum(void) +{ + print("?<"); +} + + +void SWSerLCDpa::setxy(int x, int y) +{ + print("?y"); + print(y); + print("?x"); + if (x < 10) + print('0'); + print(x); + delay(10); +} + + +void SWSerLCDpa::println(char c) +{ + print(c); + println(); +} + +void SWSerLCDpa::println(const char c[]) +{ + print(c); + println(); +} + +void SWSerLCDpa::println(uint8_t b) +{ + print(b); + println(); +} + +void SWSerLCDpa::println(int n) +{ + print(n); + println(); +} + +void SWSerLCDpa::println(long n) +{ + print(n); + println(); +} + +void SWSerLCDpa::println(unsigned long n) +{ + print(n); + println(); +} + +void SWSerLCDpa::println(long n, int base) +{ + print(n, base); + println(); +} + +// Private Methods ///////////////////////////////////////////////////////////// + +void SWSerLCDpa::printNumber(unsigned long n, uint8_t base) +{ + unsigned char buf[8 * sizeof(long)]; // Assumes 8-bit chars. + unsigned long i = 0; + + if (n == 0) { + print('0'); + return; + } + + while (n > 0) { + buf[i++] = n % base; + n /= base; + } + + for (; i > 0; i--) + print((char) (buf[i - 1] < 10 ? '0' + buf[i - 1] : 'A' + buf[i - 1] - 10)); + + delay(8); + +} diff --git a/lib/libraries/SWSerLCDpa/SWSerLCDpa.h b/lib/libraries/SWSerLCDpa/SWSerLCDpa.h new file mode 100755 index 0000000..517be49 --- /dev/null +++ b/lib/libraries/SWSerLCDpa/SWSerLCDpa.h @@ -0,0 +1,62 @@ +/* + SWSerLCDpa.h - Software serial to Peter Anderson controller chip based + LCD display library Adapted from SoftwareSerial.cpp (c) 2006 David A. Mellis + by Brian B. Riley, Underhill Center, Vermont, USA, July 2007 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef SWSerLCDpa_h +#define SWSerLCDpa_h + +#include + +class SWSerLCDpa +{ + private: + uint8_t _transmitPin; + long _baudRate; + int _bitPeriod; + void printNumber(unsigned long, uint8_t); + public: + SWSerLCDpa(uint8_t); + void begin(long); + void print(char); + void print(const char[]); + void print(uint8_t); + void print(int); + void print(unsigned int); + void print(long); + void print(unsigned long); + void print(long, int); + void println(void); + void clearscr(void); + void home(void); + void setgeo(int); + void setintensity(int); + void intoBignum(void); + void outofBignum(void); + void setxy(int, int); + void println(char); + void println(const char[]); + void println(uint8_t); + void println(int); + void println(long); + void println(unsigned long); + void println(long, int); +}; + +#endif + diff --git a/lib/libraries/SWSerLCDpa/SWSerLCDpa.o b/lib/libraries/SWSerLCDpa/SWSerLCDpa.o new file mode 100644 index 0000000000000000000000000000000000000000..e9d4ae15fb480031403131629ec9915d2369cfef GIT binary patch literal 4140 zcmd5;drXs86u);+{BS4_1;bXHC=;VtDdOV~wL?M8#uUYk#W>qS3zX0n+xh^LX#o>q zB$m?ZAdAmzGq`B<4{^~5%NA#rI5uMz_YjJ6i*ZKnMh01Q?40krx4pEI{ktz*&hK|# z_nvz%=d!P;crB%r@Q;#G5@v+ts0e`{k}o^VBNDKu)XC~ zUGxlnN;8}Cn$|RJX)@e%+&dKT1g8238Kp~`UTrc4_PZ5!zB)w(`9F!xkQ_R#J_(5|pa9Q{;?8Z4Ab6OT8KQRVq-+13U36gI}3Q9xMgRuo8#&LND+xy#bEn}X?o)&+J?HK9 zM%H(uXQ1cgk;IFmzJcDvt2cVPPPLq>p?hgT>L!&@wL_ITZ1@G|FY7DJdMhiBHflPv zy0b24&7_hqQ>|6IY3!8LjIZcL`VsZX`!EV;Bl!x{o76`24z*~p&<2{Bs#O)JHmTA_ z9FPI`;BLa7YRqV+)A~lJ!CjM^G;Da6_F1;Ioaj0s*X}zZotHkBzLS)QgV~+gKhx%P zfBeVsJ@Kd0K8yQ0?x(o;k!oQtP96@|#CL@6Xs_p-=a}b^XP?LJ(R(&}be=`9%k$;i zA^FeH%Iw|Q%ASwE2@Ujpx_j)!KI>hyCi#i~i7((AQU}#EBROM!;1d0Wek1RJIHQuW z?3uy8Ymt%__aJlF#Mt~?ttx$3u_(U{PzRzKp&rT4wWi;~Cmz<0_vPzugOlWOJyf|d z)epn*{GNqx*F&h0M-XkYDhtkEsp&NAi&Dj`q1=F?6LayJ!)p=|Uy<@RwU@SrX1T=n z3};ocwg&E}1!KcA_b?B!Mm`&!sj~VudFPcHv9AAzR%au+N}@9o-8!OMO?0ak6Wt1; zt0%fWglL@gR+q6Hq|2emYK|P{TBF8g-s2(~lhI`)nsTR;cgX*iv%m#=XdH9R2iYCV z2ngO^Uj{6wNpk`rr(={j^g$b_$Xpo6!6bhJU{EuRg6GQtEWzM@e~ZZlzhuD&AVEJZ z0`~EIC|}@8khM@yA1EdPo(u!iC&7q^!RG`>azIfgD0n`!iJz#CCBZS#789yU(Z-CM z0q2XA=hlUSf@A4`R|$MJ;FSVj5W)z8W6R)ocn!g^Lcxx8l}7M##;1ZzfyiAT5p&)O z7|#XQfcBFh`G@(=asm?hGcRo z^y@h&mVwJ!r+1jEWZYnO*h9o**H_r<>h*GC(d&`cnvD)Uwix}^4cgrBENk+Ls-tw; zXx)->bCtzbR0|6+S$0|NCZ{Ezm@HKmm$A0YVR4y>2^w1co0bZP-D$3{+e}eYEva)@ zY_8{|R`eB-%V7Q`Z&WC`BkfwP9JpbpwcP9|vO*d9o#C}b)X9=rBa<+F%>;U=cl>@a zf6D~=+9<;^d6BiYqFs?8%jH9HRyd+f`PFu-IaXUu~(f?X*Ux)oSfKUG~a}%u68m9!MHBYpArSqYGK^@FI3FWQ)3YAz@4h$4QP5uz^fv|lZ9$LyGzIzq z_Srb-Pl#n5;#5$=XA|@vAfBP3)@YuHz!@xdV&M@}e{S4z=A2N(S*t5_d z`|dS{(dVY1zs)fEW511qzJy-@@Fa%sG9P>&u+Q=JU_VBGbo-0>BOVp}V?n|Fv0tM< zzBvgXF+bv|AaR|T|0Tq1uX4~11OErmfJA@9xgfd!a)$9ODMZX-w*$sle18leF&1$- zNFH0qFz!h+!&f1X2QkZoZwBVUD|8$r=0SWCB+rBI2>MI|eK$z-LHsF5?(-#J^uhP! z8c6g(d>thB`Icd<A25vT|AS$S{Rc5ye;T|o=#M^XhVh(p t7?$6BLBE<|yu<4S{W}a}{_TR^#xUM7m!NNE81HARpzmTB_xZS>{}&6Qn4|yz literal 0 HcmV?d00001 diff --git a/lib/libraries/SWSerLCDpa/examples/K107_224_Demo/K107_224_Demo.pde b/lib/libraries/SWSerLCDpa/examples/K107_224_Demo/K107_224_Demo.pde new file mode 100644 index 0000000..25ed7d9 --- /dev/null +++ b/lib/libraries/SWSerLCDpa/examples/K107_224_Demo/K107_224_Demo.pde @@ -0,0 +1,83 @@ +/* + Arduino - code using SoftwareSerial to write to a Serial LCD, + specifically a Wulfden K107 board utilizing an Anderson LCD 117 + controller chip. At 9600 baud, a 1ms delay plus various long + pauses suffice to keep uffer overrun in the controller. + + Brian B. Riley, Underhill Center, Vermont + 16 July 2007 + 03 Sept 2007 modified for 2x24 + +*/ + +#include + +#define txPin 14 +#define LEDpin 13 + + int testval = 0; + + // create a new instance of a software serial port + SWSerLCDpa mySerial = SWSerLCDpa(txPin); + +void setup() { + + pinMode(txPin, OUTPUT); // define pin modes for tx pin + pinMode(LEDpin, OUTPUT); // define pin modes for LED pin + + mySerial.begin(9600); // set the data rate for the SoftwareSerial port + mySerial.clearscr(); // 'prime the pump' - send something unimportant + // to kickstart the software serial in the LCD driver + mySerial.setgeo(224); // set chip geometry for a 2x24 display + // mySerial.setintensity(0x80); // set backlight at max intensity (if there is a backlight) +} + +void loop() { + + digitalWrite(LEDpin, HIGH); // light the LED + mySerial.clearscr(); // setup for simple lines of text + mySerial.println(" Hello Arduino!"); + mySerial.print(" 2x24 ser_LCD display"); + + delay(3000); // setup to print large number in various bases + mySerial.clearscr(); // at various xy positions + + digitalWrite(LEDpin, LOW); // turn the LED off + + mySerial.setxy(0,1); // sest cursor at (x,y) + mySerial.print(4567, 5); // prints that decinmal number in base 5 + + delay(500); + mySerial.setxy(9,0); + mySerial.print(657, 2); + delay (500); + mySerial.setxy(15,1); + mySerial.print(567, 3); + delay(500); + mySerial.setxy(3,0); + mySerial.print("aBcDe"); + + digitalWrite(LEDpin, HIGH); // light the LED + + delay(2000); + mySerial.clearscr(); + mySerial.setxy(8,0); + mySerial.print("-->"); + mySerial.print(testval++ % 10); + mySerial.print("<--"); + + delay(2000); // shows printable character set + mySerial.clearscr(); + for (byte i = 0x30 ; i< 0x80 ; i++) { + mySerial.print(i); + delay(100); + } + + digitalWrite(LEDpin, LOW); // turn the LED off + + delay(2000); // wait 2 seconds (2000 ms) + +} + + + diff --git a/lib/libraries/SWSerLCDpa/examples/K107_240Demo/K107_240Demo.pde b/lib/libraries/SWSerLCDpa/examples/K107_240Demo/K107_240Demo.pde new file mode 100644 index 0000000..73ad0b5 --- /dev/null +++ b/lib/libraries/SWSerLCDpa/examples/K107_240Demo/K107_240Demo.pde @@ -0,0 +1,86 @@ +/* + Arduino - code using SoftwareSerial to write to a Serial LCD, + specifically a Wulfden K107 board utilizing an Anderson LCD 117 + controller chip. At 9600 baud, a 1ms delay plus various long + pauses suffice to keep uffer overrun in the controller. + + Brian B. Riley, Underhill Center, Vermont + 16 July 2007 + 03 Sept 2007 modified for 2x24 + +*/ + +#include + +#define txPin 14 +#define LEDpin 13 + + int testval = 0; + + // create a new instance of a software serial port + SWSerLCDpa mySerial = SWSerLCDpa(txPin); + +void setup() { + + pinMode(txPin, OUTPUT); // define pin modes for tx pin + pinMode(LEDpin, OUTPUT); // define pin modes for LED pin + + mySerial.begin(9600); // set the data rate for the SoftwareSerial port + mySerial.clearscr(); // 'prime the pump' - send something unimportant + // to kickstart the software serial in the LCD driver + mySerial.setgeo(240); // set chip geometry for a 2x24 display + // mySerial.setintensity(0x80); // set backlight at max intensity (if there is a backlight) +} + +void loop() { + + digitalWrite(LEDpin, HIGH); // light the LED + mySerial.clearscr(); // setup for simple lines of text + mySerial.println(" Hello Arduino!"); + mySerial.print(" 2x40 ser_LCD display"); + + delay(3000); // setup to print large number in various bases + mySerial.clearscr(); // at various xy positions + + digitalWrite(LEDpin, LOW); // turn the LED off + + mySerial.setxy(0,1); // sest cursor at (x,y) + mySerial.print(4567, 5); // prints that decinmal number in base 5 + + delay(500); + mySerial.setxy(9,0); + mySerial.print(657, 2); + delay (500); + mySerial.setxy(29,1); + mySerial.print(567, 3); + delay(500); + mySerial.setxy(23,0); + mySerial.print("aBcDe"); + delay (500); + mySerial.setxy(20,1); + mySerial.print("urbaop"); + + digitalWrite(LEDpin, HIGH); // light the LED + + delay(2000); + mySerial.clearscr(); + mySerial.setxy(18,0); + mySerial.print("-->"); + mySerial.print(testval++ % 10); + mySerial.print("<--"); + + delay(2000); // shows printable character set + mySerial.clearscr(); + for (byte i = 0x30 ; i< 0x80 ; i++) { + mySerial.print(i); + delay(100); + } + + digitalWrite(LEDpin, LOW); // turn the LED off + + delay(2000); // wait 2 seconds (2000 ms) + +} + + + diff --git a/lib/libraries/SWSerLCDpa/examples/K107_LCD_Plot/K107_LCD_Plot.pde b/lib/libraries/SWSerLCDpa/examples/K107_LCD_Plot/K107_LCD_Plot.pde new file mode 100644 index 0000000..0cd5adf --- /dev/null +++ b/lib/libraries/SWSerLCDpa/examples/K107_LCD_Plot/K107_LCD_Plot.pde @@ -0,0 +1,152 @@ +/* + This ia an excerise to show how a character based + LCD even a two line be can be used to do simple + scrolling bar graphs. based upon work done by + Peter H. Anderson at Morgan State College + May 10, '07 + + Brian B. Riley, Underhill Center, VT + 4 Sept 2007 + +*/ + +#include + +#define txPin 14 +#define LEDpin 13 +#define DISPGEO 224 // no not a typo, needs to be this way + // due to a bug in the LCD controller + // discovere while doing this program +#define DCOLs 24 // # of Display COLumns +#define DROWs 2 // # of Display ROWs +#define NUMDATs 42 + + // create a new instance of a software mySerial port + SWSerLCDpa mySerial = SWSerLCDpa(txPin); + +void setup() +{ + + pinMode(txPin, OUTPUT); // define pin modes for tx pin + + mySerial.begin(9600); + delay(50); + mySerial.clearscr(); + mySerial.setgeo(DISPGEO); + mySerial.clearscr(); + + delay(1000); + + mySerial.print("?D00000000000000000"); // define special characters in LCD + delay(250); // delay to allow write to EEPROM + mySerial.print("?D1000000000000001f"); + delay(250); + mySerial.print("?D20000000000001f1f"); + delay(250); + mySerial.print("?D300000000001f1f1f"); + delay(250); + mySerial.print("?D4000000001f1f1f1f"); + delay(250); + mySerial.print("?D50000001f1f1f1f1f"); + delay(250); + mySerial.print("?D600001f1f1f1f1f1f"); + delay(250); + mySerial.print("?D7001f1f1f1f1f1f1f"); + delay(250); +} + +void loop() +{ + int i, display_array[DCOLs], temperature, scale32; + + mySerial.clearscr(); + delay(100); + + for (i=0; i 2) + { + return(2 * temp_readings[n]); + } + else + { + return(temp_readings[n]); + } +} + + +void plot_bar(int hght, int col) +{ + int n, row; + + for (n=0; n < DROWs; n++) + { + row = (DROWs-1) - n; + mySerial.setxy(col, row); + + if (hght >=7) + { + mySerial.print("?7"); // lay down a full block + hght = hght - 7; + } + else + { + mySerial.print("?"); + mySerial.print(hght); + hght = 0; + } + } +} diff --git a/lib/libraries/SWSerLCDpa/examples/LCDPHA_216_Demo/LCDPHA_216_Demo.pde b/lib/libraries/SWSerLCDpa/examples/LCDPHA_216_Demo/LCDPHA_216_Demo.pde new file mode 100644 index 0000000..2bd5665 --- /dev/null +++ b/lib/libraries/SWSerLCDpa/examples/LCDPHA_216_Demo/LCDPHA_216_Demo.pde @@ -0,0 +1,97 @@ +/* + Arduino - code using SoftwareSerial to write to a Serial LCD, + specifically a Wulfden K107 board utilizing an Anderson LCD 117 + controller chip. + + The code below specifies 19,200 baud, which is a special version + I have from PHA. It works fine but if you deliver characters to + the chip to rapidly it will overrun the chip so a 2ms delay + between characters is executed plus a long pause after about + 60-70 characters. + + At 9600 baud, a 1ms delay plus the long pause suffices. + + I am considering writing a new Software Serial library and + calling it SWSerLCDpa and have only the transmit routines for + Writing to a device with no Receiver code. It will eliminate the + need to specify a Receive pin and tie it up doing nothing and + the code will be a bit smaller. + + Brian B. Riley, Underhill Center, Vermont + 16 July 2007 + +*/ + + +#include + +#define txPin 14 +int intensity = 0; + +// set up a new serial port +SWSerLCDpa mySerial = SWSerLCDpa(txPin); + +int testval = 0; + +void setup() { + + pinMode(txPin, OUTPUT); // define pin modes for tx pin + + mySerial.begin(9600); // set the data rate for the SoftwareSerial port + mySerial.clearscr(); + mySerial.setgeo(216); // set chip geometry for a 2x16 display + mySerial.setintensity(0x00); // set backlight at max intensity +} + +void loop() { + + mySerial.setintensity(intensity); // set backlight at max intensity + + if (intensity < 0xC0) + intensity += 0x40; + else + intensity = 0x00; + + + mySerial.clearscr(); // setup for simple lines of text + mySerial.println(" Hello Arduino!"); + mySerial.print("ser_LCD display"); + + delay(3000); // setup to print large number in various bases + mySerial.clearscr(); // at various xy positions + + mySerial.setxy(0,1); + mySerial.print(4567, 5); + + delay(500); + mySerial.setxy(6,0); + mySerial.print(657, 2); + delay (500); + mySerial.setxy(9,1); + mySerial.print(567, 3); + delay(500); + mySerial.setxy(0,0); + mySerial.print("aBcDe"); + + delay(2000); + mySerial.clearscr(); + mySerial.setxy(4,0); // value in the original Softwae Serial code + mySerial.print("-->"); + mySerial.print(testval++ % 10); + mySerial.print("<--"); + + delay(2000); // shows printable character set + mySerial.clearscr(); + for (byte i = 0x30 ; i< 0x80 ; i++) { + mySerial.print(i); + delay(100); + } + + delay(2000); + + + +} + + + diff --git a/lib/libraries/SWSerLCDpa/examples/LCDPHA_420Demo/LCDPHA_420Demo.pde b/lib/libraries/SWSerLCDpa/examples/LCDPHA_420Demo/LCDPHA_420Demo.pde new file mode 100644 index 0000000..12ffce2 --- /dev/null +++ b/lib/libraries/SWSerLCDpa/examples/LCDPHA_420Demo/LCDPHA_420Demo.pde @@ -0,0 +1,113 @@ +/* + Arduino - code using SoftwareSerial to write to a Serial LCD, + specifically a Wulfden K107 board utilizing an Anderson LCD 117 + controller chip. + + The code below specifies 19,200 baud, which is a special version + I have from PHA. It works fine but if you deliver characters to + the chip to rapidly it will overrun the chip so a 2ms delay + between characters is executed plus a long pause after about + 60-70 characters. + + At 9600 baud, a 1ms delay plus the long pause suffices. + + I am considering writing a new Software Serial library and + calling it SWSerLCDpa and have only the transmit routines for + Writing to a device with no Receiver code. It will eliminate the + need to specify a Receive pin and tie it up doing nothing and + the code will be a bit smaller. + + Brian B. Riley, Underhill Center, Vermont + 16 July 2007 + +*/ + + +#include + +#define txPin 14 // Analog 0 is also Digital 14, but any pin will do other than 0 ad 1 + +// set up a new serial port +SWSerLCDpa mySerial = SWSerLCDpa(txPin); + +int testval = 0; + +void setup() { + + pinMode(txPin, OUTPUT); // define pin modes for tx pin + + mySerial.begin(9600); // set the data rate for the SoftwareSerial port + mySerial.clearscr(); // send any old command to 'prime the pump!" + mySerial.setgeo(420); // set chip geometry for a 4x20 display + mySerial.setintensity(0x80); // set backlight at max intensity + +//mySerial.print("?C012345678901234567890"); // this comment line gives you character count grid for the lines below + mySerial.print("?C0===================="); // set custom splash screen + delay(200); + mySerial.print("?C1 The Shoppe at "); + delay(200); + mySerial.print("?C2 Wulfden "); + delay(200); + mySerial.print("?C3===================="); + delay(200); + mySerial.print("?S2"); + delay(200); + +} + +void loop() { + + mySerial.clearscr(); // setup for simple lines of text + mySerial.setxy(0,0); + mySerial.println(" Hello Arduino"); + mySerial.setxy(0,1); + mySerial.println(" this is a"); + mySerial.setxy(0,2); + mySerial.println(" serial LCD"); + + delay(5000); // setup to print large number in various bases + mySerial.clearscr(); // at various xy positions + + mySerial.setxy(6,3); + mySerial.print(4567, 5); + + delay(500); + mySerial.setxy(3,1); + mySerial.print(457, 2); + delay (500); + mySerial.setxy(3,2); + mySerial.print(4567, 3); + delay(500); + mySerial.setxy(10,0); + mySerial.print("aBc"); + + delay(2000); // I used this to find the bug when printing a zero + mySerial.clearscr(); + mySerial.setxy(6,1); // value in the original Softwae Serial code + mySerial.print("-->"); + mySerial.print(testval++ % 10); + mySerial.print("<--"); + + + delay(2000); // setup to print Big NUmbers on a 4x20 display + mySerial.clearscr(); + + for (int i = 101 ; i < 300 ; i += 7) { + mySerial.setxy(0,0); + mySerial.intoBignum(); + mySerial.print(i, 10); + mySerial.outofBignum(); + mySerial.print(i % 33); + delay (300); + } + + delay(2000); + mySerial.print("?*"); + delay(2000); + + + +} + + + diff --git a/lib/libraries/SWSerLCDpa/examples/LCD_Plot224/LCD_Plot224.pde b/lib/libraries/SWSerLCDpa/examples/LCD_Plot224/LCD_Plot224.pde new file mode 100644 index 0000000..d97c7ee --- /dev/null +++ b/lib/libraries/SWSerLCDpa/examples/LCD_Plot224/LCD_Plot224.pde @@ -0,0 +1,153 @@ +// LCD_PLOT - Arduino +// +// Tx ---------------------------- To mySerial LCD (LCD #117) +// +// Peter H Anderson, Baltimore, MD, May 10, '07 + +#include + +#define txPin 14 +#define LEDpin 13 +#define DISPGEO 224 +#define DCOLs 24 // # of Display COLumns +#define DROWs 2 // # of Display ROWs +#define NUMDATs 42 + + // create a new instance of a software mySerial port + SWSerLCDpa mySerial = SWSerLCDpa(txPin); + +void setup() +{ + + pinMode(txPin, OUTPUT); // define pin modes for tx pin + + mySerial.begin(9600); + delay(50); + mySerial.clearscr(); + mySerial.setgeo(DISPGEO); + + mySerial.print("?D00000000000000000"); // define special characters + delay(200); // delay to allow write to EEPROM + mySerial.print("?D1000000000000001f"); + delay(200); + mySerial.print("?D20000000000001f1f"); + delay(200); + mySerial.print("?D300000000001f1f1f"); + delay(200); + mySerial.print("?D4000000001f1f1f1f"); + delay(200); + mySerial.print("?D50000001f1f1f1f1f"); + delay(200); + mySerial.print("?D600001f1f1f1f1f1f"); + delay(200); + mySerial.print("?D7001f1f1f1f1f1f1f"); + delay(200); +} + +void loop() +{ + int i, display_array[DCOLs], temperature, scale32; + + mySerial.clearscr(); + delay(100); + + for (i=0; i 2) + { + return(2 * temp_readings[n]); + } + else + { + return(temp_readings[n]); + } +} + + +void plot_bar(int hght, int col) +{ + int n, row; + + for (n=0; n < DROWs; n++) // <4 + { + row = (DROWs-1) - n; // 3-n + mySerial.print("?x"); + if(col < 10) + { + mySerial.print("0"); // be sure it is two characters + } + mySerial.print(col); + + mySerial.print("?y"); + mySerial.print(row); + + if (hght >=7) + { + mySerial.print("?7"); // lay down a full block + hght = hght - 7; + } + else + { + mySerial.print("?"); + mySerial.print(hght); + hght = 0; + } + delay(5); + } +} diff --git a/lib/libraries/SWSerLCDpa/keywords.txt b/lib/libraries/SWSerLCDpa/keywords.txt new file mode 100644 index 0000000..de5a74c --- /dev/null +++ b/lib/libraries/SWSerLCDpa/keywords.txt @@ -0,0 +1,18 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SoftwareSerial KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/lib/rad.rb b/lib/rad.rb new file mode 100644 index 0000000..d8a1617 --- /dev/null +++ b/lib/rad.rb @@ -0,0 +1,5 @@ +module Rad +end + +require 'rad/version' +require 'rad/init' \ No newline at end of file diff --git a/lib/rad/antiquated_todo.txt b/lib/rad/antiquated_todo.txt new file mode 100644 index 0000000..d56edc6 --- /dev/null +++ b/lib/rad/antiquated_todo.txt @@ -0,0 +1,47 @@ +-framework for translation to C (wrapper for RubyToAnsiC) +-framework for translation testing (want to test that each Rad method translates to the expected C) + +-have to replace symbol & str definitions with "char name[]" ? + +-maybe can't build rad.c dynamically? can use as helpers in writing it statically rad.rb and RubyToAnsiC as helpers in writing it. . . +-maybe there's a way to give RubyToC prototypes for the arduino library methods (or maybe include them directly) so that it will be able to figure out the signatures for my methods (otherwise it breaks because, for example, it can't figure out what read returns). +-speaking of which: all rad methods need to have a statically typed return value or else they won't be translatable to C. For example, as it stands #read may return a bool (if it calls digitalRead) or an int (if it calls analogRead). This may force me to take away much of the syntactic sugar + +-how to handle setting up named pins: + +declare the name with an underscore: + int _led = 1; +and define an accessor for it: + int led(){ + _led; + } +Then, calling the method will get you the int. + +-drop read and write custom methods (at least for now)? save time, save a whole step. Make it trivial to implement the whole library. . . + +====================================================================================================== +-the Arduino DSL is pretty good. Don't try to reinvent it for the board's api, add value on top of it! +====================================================================================================== + +-add constants HIGH, LOW, etc. + +-design directory structure +-->where do we put the sketch files +-->where do the .c files get compiled to? + +-design rad lib directory structure +/sketch + my_sketch.rb +/build + my_sketch.c + rad.c +/rad + /lib + /specs + Rakefile + + +-helper methods like blink + +-proof of concept of build process + diff --git a/lib/rad/arduino_sketch.rb b/lib/rad/arduino_sketch.rb new file mode 100644 index 0000000..a22cb03 --- /dev/null +++ b/lib/rad/arduino_sketch.rb @@ -0,0 +1,533 @@ +# ArduinoSketch is the main access point for working with RAD. Sub-classes of ArduinoSketch have access to a wide array of convenient class methods (documented below) for doing the most common kinds of setup needed when programming the Arduino such as configuring input and output pins and establishing serial connections. Here is the canonical 'hello world' example of blinking a single LED in RAD: +# +# class HelloWorld < ArduinoSketch +# output_pin 13, :as => led +# +# def loop +# blink 13, 500 +# end +# end +# +# As you can see from this example, your ArduinoSketch sub-class can be dividied into two-parts: class methods for doing configuration and a loop method which will be run repeatedly at the Arduino's clock rate. Documentation for the various available class methods is below. The ArduinoSketch base class is designed to work with a series of rake tasks to automatically translate your loop method into C++ for compilation by the Arduino toolchain (see link://files/lib/rad/tasks/build_and_make_rake.html for details). See http://rad.rubyforge.org/examples for lots more examples of usage. +# +# ==Arduino built-in methods +# Thanks to this translation process you can take advantage of the complete Arduino software API (full docs here: http://www.arduino.cc/en/Reference/HomePage). What follows is the core of a RAD-Arduino dictionary for translating between RAD methods and the Arduino functionality they invoke, N.B. many Arduino methods have been left out (including the libraries for Time, Math, and Random Numbers, as the translation between them and their RAD counterparts should be relatively straightforward after perusing the examples here). For further details on each method, visit their Arduino documenation. +# +# Digital I/O +# +# digital_write(pin, value) +# +# Arduino method: digitalWrite(pin, value) +# +# Description: "Ouputs either HIGH or LOW at a specified pin." +# +# Documentation: http://www.arduino.cc/en/Reference/DigitalWrite +# +# digital_read(pin) +# +# Arduino method: digitalRead(pin) +# +# Description: "Reads the value from a specified pin, it will be either HIGH or LOW." +# +# Documentation: http://www.arduino.cc/en/Reference/DigitalRead +# +# Analog I/O +# +# analog_read(pin) +# +# Arduino method: analogRead(pin) +# +# Description: "Reads the value from the specified analog pin. The Arduino board contains a 6 channel +# (8 channels on the Mini), 10-bit analog to digital converter. This means that it will map input +# voltages between 0 and 5 volts into integer values between 0 and 1023. This yields a resolution +# between readings of: 5 volts / 1024 units or, .0049 volts (4.9 mV) per unit. It takes about 100 +# us (0.0001 s) to read an analog input, so the maximum reading rate is about 10,000 times a second." +# +# Documentation: http://www.arduino.cc/en/Reference/AnalogRead +# +# analog_write(pin, value) +# +# Arduino method: analogWrite(pin, value) +# +# Description: "Writes an analog value (PWM wave) to a pin. On newer Arduino boards (including the Mini +# and BT) with the ATmega168 chip, this function works on pins 3, 5, 6, 9, 10, and 11. Older USB and +# serial Arduino boards with an ATmega8 only support analogWrite() on pins 9, 10, and 11. Can be used +# to light a LED at varying brightnesses or drive a motor at various speeds. After a call to analogWrite, +# the pin will generate a steady wave until the next call to analogWrite (or a call to digitalRead or +# digitalWrite on the same pin)." +# +# Documentation: http://www.arduino.cc/en/Reference/AnalogWrite +# +# Serial Communication +# +# serial_available() +# +# Arduino method: Serial.available() +# +# Description: "Get the number of bytes (characters) available for reading over the serial port. +# Returns the number of bytes available to read in the serial buffer, or 0 if none are +# available. If any data has come in, Serial.available() will be greater than 0. The serial buffer +# can hold up to 128 bytes." +# +# Documentation: http://www.arduino.cc/en/Serial/Available +# +# serial_read() +# +# Arduino method: Serial.read() +# +# Description: "Reads incoming serial data and returns the first byte of incoming serial data +# available (or -1 if no data is available)" +# +# Documentation: http://www.arduino.cc/en/Serial/Read +# +# serial_print(data) +# +# Arduino method: Serial.print(data) +# +# Description: "Prints data to the serial port." +# +# Documentation: http://www.arduino.cc/en/Serial/Print +# +# serial_println(data) +# +# Arduino method: Serial.println(data) +# +# Description: "Prints a data to the serial port, followed by a carriage return character +# (ASCII 13, or '\r') and a newline character (ASCII 10, or '\n'). This command takes the +# same forms as Serial.print():" +# +# Documentation: http://www.arduino.cc/en/Serial/Println +# +# serial_flush() +# +# Arduino method: Serial.flush() +# +# Description: "Flushes the buffer of incoming serial data. That is, any call to Serial.read() +# or Serial.available() will return only data received after the most recent call +# to Serial.flush()." +# +# Documentation: http://www.arduino.cc/en/Serial/Flush +# +class ArduinoSketch + + def initialize #:nodoc: + @declarations = [] + @pin_modes = {:output => [], :input => []} + @pullups = [] + @other_setup = [] # specifically, Serial.begin + @assembler_declarations = [] + @accessors = [] + @signatures = ["void blink();", "int main();"] + + helper_methods = [] + helper_methods << "void blink(int pin, int ms) {" + helper_methods << "\tdigitalWrite( pin, HIGH );" + helper_methods << "\tdelay( ms );" + helper_methods << "\tdigitalWrite( pin, LOW );" + helper_methods << "\tdelay( ms );" + helper_methods << "}" + @helper_methods = helper_methods.join( "\n" ) + end + + # Setup variables outside of the loop. Does some magic based on type of arguments. Subject to renaming. Use with caution. + def vars(opts={}) + opts.each do |k,v| + if v.class == Symbol + @declarations << "#{v} _#{k};" + @accessors << <<-CODE + #{v} #{k}(){ + \treturn _#{k}; + } + CODE + else + type = case v + when Integer + "int" + when String + "char*" + when TrueClass + "bool" + when FalseClass + "bool" + end + + @declarations << "#{type} _#{k} = #{v};" + @accessors << <<-CODE + #{type} #{k}(){ + \treturn _#{k}; + } + CODE + end + end + end + + # Confiugre a single pin for output and setup a method to refer to that pin, i.e.: + # + # output_pin 7, :as => :led + # + # would configure pin 7 as an output and let you refer to it from the then on by calling + # the `led` method in your loop like so: + # + # def loop + # digital_write led, ON + # end + # + def output_pin(num, opts={}) + raise ArgumentError, "can only define pin from Fixnum, got #{num.class}" unless num.is_a?(Fixnum) + @pin_modes[:output] << num + if opts[:as] + @declarations << "int _#{opts[ :as ]} = #{num};" + + accessor = [] + accessor << "int #{opts[ :as ]}() {" + accessor << "\treturn _#{opts[ :as ]};" + accessor << "}" + @accessors << accessor.join( "\n" ) + + @signatures << "int #{opts[ :as ]}();" + end + end + + # Like ArduinoSketch#output_pin but configure more than one output pin simultaneously. Takes an array of pin numbers. + def output_pins(nums) + ar = Array(nums) + ar.each {|n| output_pin(n)} + end + + # Confiugre a single pin for input and setup a method to refer to that pin, i.e.: + # + # input_pin 3, :as => :button + # + # would configure pin 3 as an input and let you refer to it from the then on by calling + # the `button` method in your loop like so: + # + # def loop + # digital_write led if digital_read button + # end + # + def input_pin(num, opts={}) + raise ArgumentError, "can only define pin from Fixnum, got #{num.class}" unless num.is_a?(Fixnum) + @pin_modes[:input] << num + if opts[:as] + @declarations << "int _#{opts[ :as ]} = #{num};" + + accessor = [] + accessor << "int #{opts[ :as ]}() {" + accessor << "\treturn _#{opts[ :as ]};" + accessor << "}" + @accessors << accessor.join( "\n" ) + + @signatures << "int #{opts[ :as ]}();" + end + @pullups << num if opts[:as] + end + + # Like ArduinoSketch#input_pin but configure more than one input pin simultaneously. Takes an array of pin numbers. + def input_pins(nums) + ar = Array(nums) + ar.each {|n| input_pin(n)} + end + + def add(st) #:nodoc: + @helper_methods << "\n#{st}\n" + end + + # Configure Arduino for serial communication. Optionally, set the baud rate: + # + # serial_begin :rate => 2400 + # + # default is 9600. See http://www.arduino.cc/en/Serial/Begin for more details. + # + def serial_begin(opts={}) + rate = opts[:rate] ? opts[:rate] : 9600 + @other_setup << "Serial.begin(#{rate});" + end + + # Treat a pair of digital I/O pins as a serial line. See: http://www.arduino.cc/en/Tutorial/SoftwareSerial + def software_serial(rx, tx, opts={}) + raise ArgumentError, "can only define rx from Fixnum, got #{rx.class}" unless rx.is_a?(Fixnum) + raise ArgumentError, "can only define tx from Fixnum, got #{tx.class}" unless tx.is_a?(Fixnum) + + output_pin(tx) + + rate = opts[:rate] ? opts[:rate] : 9600 + if opts[:as] + @declarations << "SoftwareSerial _#{opts[ :as ]} = SoftwareSerial(#{rx}, #{tx});" + accessor = [] + accessor << "SoftwareSerial& #{opts[ :as ]}() {" + accessor << "\treturn _#{opts[ :as ]};" + accessor << "}" + accessor << "int read(SoftwareSerial& s) {" + accessor << "\treturn s.read();" + accessor << "}" + accessor << "void println( SoftwareSerial& s, char* str ) {" + accessor << "\treturn s.println( str );" + accessor << "}" + accessor << "void print( SoftwareSerial& s, char* str ) {" + accessor << "\treturn s.print( str );" + accessor << "}" + accessor << "void println( SoftwareSerial& s, int i ) {" + accessor << "\treturn s.println( i );" + accessor << "}" + accessor << "void print( SoftwareSerial& s, int i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + @accessors << accessor.join( "\n" ) + + @signatures << "SoftwareSerial& #{opts[ :as ]}();" + + @other_setup << "_#{opts[ :as ]}.begin(#{rate});" + end + end + + def swser_LCDpa(tx, opts={}) + raise ArgumentError, "can only define tx from Fixnum, got #{tx.class}" unless tx.is_a?(Fixnum) + output_pin(tx) + + rate = opts[:rate] ? opts[:rate] : 9600 + if opts[:as] + @declarations << "SWSerLCDpa _#{opts[ :as ]} = SWSerLCDpa(#{tx});" + accessor = [] + accessor << "SWSerLCDpa& #{opts[ :as ]}() {" + accessor << "\treturn _#{opts[ :as ]};" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, uint8_t b ) {" + accessor << "\treturn s.print( b );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, const char *str ) {" + accessor << "\treturn s.print( str );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, char c ) {" + accessor << "\treturn s.print( c );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, int i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, unsigned int i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, long i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, unsigned long i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, long i, int b ) {" + accessor << "\treturn s.print( i, b );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, char* str ) {" + accessor << "\treturn s.println( str );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, char* str ) {" + accessor << "\treturn s.print( str );" + accessor << "}" + accessor << "void println(SWSerLCDpa& s) {" + accessor << "\treturn s.println();" + accessor << "}" + accessor << "void clearscr(SWSerLCDpa& s) {" + accessor << "\treturn s.clearscr();" + accessor << "}" + accessor << "void home(SWSerLCDpa& s) {" + accessor << "\treturn s.home();" + accessor << "}" + accessor << "void setgeo( SWSerLCDpa& s, int i ) {" + accessor << "\treturn s.setgeo( i );" + accessor << "}" + accessor << "void setintensity( SWSerLCDpa& s, int i ) {" + accessor << "\treturn s.setintensity( i );" + accessor << "}" + accessor << "void intoBignum(SWSerLCDpa& s) {" + accessor << "\treturn s.intoBignum();" + accessor << "}" + accessor << "void outofBignum(SWSerLCDpa& s) {" + accessor << "\treturn s.outofBignum();" + accessor << "}" + accessor << "void setxy( SWSerLCDpa& s, int x, int y) {" + accessor << "\treturn s.setxy( x, y );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, char c ) {" + accessor << "\treturn s.println( c );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, const char c[] ) {" + accessor << "\treturn s.println( c );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, uint8_t b ) {" + accessor << "\treturn s.println( b );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, int i ) {" + accessor << "\treturn s.println( i );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, long i ) {" + accessor << "\treturn s.println( i );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, unsigned long i ) {" + accessor << "\treturn s.println( i );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, long i, int b ) {" + accessor << "\treturn s.println( i, b );" + accessor << "}" + @accessors << accessor.join( "\n" ) + + @signatures << "SWSerLCDpa& #{opts[ :as ]}();" + + @other_setup << "_#{opts[ :as ]}.begin(#{rate});" + end + end + + def compose_setup #:nodoc: also composes headers and signatures + result = [] + + result << comment_box( "Auto-generated by RAD" ) + + result << "#include \n" + result << "#include \n" + result << "#include " + + + result << comment_box( 'method signatures' ) + result << "void loop();" + result << "void setup();" + @signatures.each {|sig| result << sig} + + result << "\n" + comment_box( "variable and accessors" ) + @declarations.each {|dec| result << dec} + result << "" # blank line + @accessors.each {|ac| result << ac} + + result << "\n" + comment_box( "assembler declarations" ) + unless @assembler_declarations.empty? + result << <<-CODE + extern "C" { + #{@assembler_declarations.join("\n")} + } + CODE + end + + result << "\n" + comment_box( "setup" ) + result << "void setup() {" + result << "\t// pin modes" + + @pin_modes.each do |k,v| + v.each do |value| + result << "\tpinMode(#{value}, #{k.to_s.upcase});" + end + end + + @pullups.each do |pin| + result << "\tdigitalWrite( #{pin}, HIGH ); // enable pull-up resistor for input" + end + + unless @other_setup.empty? + result << "\t// other setup" + result << @other_setup.join( "\n" ) + end + + result << "}\n" + + result << comment_box( "helper methods" ) + result << "\n// RAD built-in helpers" + result << @helper_methods.lstrip + + result << "\n// serial helpers" + result << serial_boilerplate.lstrip + + result << "\n" + comment_box( "main() function" ) + result << "int main() {" + result << "\tinit();" + result << "\tsetup();" + result << "\tfor( ;; ) { loop(); }" + result << "\treturn 0;" + result << "}" + + result << "\n" + comment_box( "loop! Autogenerated by RubyToC, sorry it's ugly." ) + + return result.join( "\n" ) + end + + + # Write inline assembler code. 'Name' is a symbol representing the name of the function to be defined in the assembly code; 'signature' is the function signature for the function being defined; and 'code' is the assembly code itself (both of these last two arguments are strings). See an example here: http://rad.rubyforge.org/examples/assembler_test.html + def assembler(name, signature, code) + @assembler_declarations << signature + assembler_code = <<-CODE + .file "#{name}.S" + .arch #{Makefile.hardware_params['mcu']} + .global __do_copy_data + .global __do_clear_bss + .text + .global #{name} + .type #{name}, @function + #{code} + CODE + + File.open(File.expand_path("#{RAD_ROOT}") + "/#{PROJECT_DIR_NAME}/#{name}.S", "w"){|f| f << assembler_code} + end + + def self.pre_process(sketch_string) #:nodoc: + result = sketch_string + result.gsub!("HIGH", "1") + result.gsub!("LOW", "0") + result.gsub!("ON", "1") + result.gsub!("OFF", "0") + result + end + + private + + def serial_boilerplate #:nodoc: + out = [] + out << "int serial_available() {" + out << "\treturn (Serial.available() > 0);" + out << "}" + + out << "char serial_read() {" + out << "\treturn (char) Serial.read();" + out << "}" + + out << "void serial_flush() {" + out << "\treturn Serial.flush();" + out << "}" + + out << "void serial_print( char str ) {" + out << "\treturn Serial.print( str );" + out << "}" + + out << "void serial_print( char* str ) {" + out << "\treturn Serial.print( str );" + out << "}" + + out << "void serial_print( int i ) {" + out << "\treturn Serial.print( i );" + out << "}" + + out << "void serial_print( long i ) {" + out << "\treturn Serial.print( i );" + out << "}" + + out << "void serial_println( char* str ) {" + out << "\treturn Serial.println( str );" + out << "}" + + out << "void serial_println( char str ) {" + out << "\treturn Serial.println( str );" + out << "}" + + out << "void serial_println( int i ) {" + out << "\treturn Serial.println( i );" + out << "}" + + out << "void serial_println( long i ) {" + out << "\treturn Serial.println( i );" + out << "}" + + return out.join( "\n" ) + end + + def comment_box( content ) #:nodoc: + out = [] + out << "/" * 74 + out << "// " + content + out << "/" * 74 + + return out.join( "\n" ) + end +end \ No newline at end of file diff --git a/lib/rad/generators/makefile/makefile.erb b/lib/rad/generators/makefile/makefile.erb new file mode 100644 index 0000000..b173cb6 --- /dev/null +++ b/lib/rad/generators/makefile/makefile.erb @@ -0,0 +1,243 @@ +# Arduino makefile +# +# This makefile allows you to build sketches from the command line +# without the Arduino environment (or Java). +# +# The Arduino environment does preliminary processing on a sketch before +# compiling it. If you're using this makefile instead, you'll need to do +# a few things differently: +# +# - Give your program's file a .cpp extension (e.g. foo.cpp). +# +# - Put this line at top of your code: #include +# +# - Write prototypes for all your functions (or define them before you +# call them). A prototype declares the types of parameters a +# function will take and what type of value it will return. This +# means that you can have a call to a function before the definition +# of the function. A function prototype looks like the first line of +# the function, with a semi-colon at the end. For example: +# int digitalRead(int pin); +# +# - Write a main() function for your program that returns an int, calls +# init() and setup() once (in that order), and then calls loop() +# repeatedly(): +# +# int main() +# { +# init(); +# setup(); +# +# for (;;) +# loop(); +# +# return 0; +# } +# +# Instructions for using the makefile: +# +# 1. Copy this file into the folder with your sketch. +# +# 2. Below, modify the line containing "TARGET" to refer to the name of +# of your program's file without an extension (e.g. TARGET = foo). +# +# 3. Modify the line containg "ARDUINO" to point the directory that +# contains the Arduino core (for normal Arduino installations, this +# is the hardware/cores/arduino sub-directory). +# +# 4. Modify the line containing "PORT" to refer to the filename +# representing the USB or serial connection to your Arduino board +# (e.g. PORT = /dev/tty.USB0). If the exact name of this file +# changes, you can use * as a wildcard (e.g. PORT = /dev/tty.USB*). +# +# 5. At the command line, change to the directory containing your +# program's file and the makefile. +# +# 6. Type "make" and press enter to compile/verify your program. +# +# 7. Type "make upload", reset your Arduino board, and press enter to +# upload your program to the Arduino board. +# +# $Id$ + +PORT = <%= params['serial_port'] %> +TARGET = <%= params['target'] %> +ARDUINO = <%= params['arduino_root'] %>/hardware/cores/arduino +SOFTWARE_SERIAL = <%= params['arduino_root'] %>/hardware/libraries/SoftwareSerial +LIBRARY_ROOT = <%= params['libraries_root'] %> +SRC = $(ARDUINO)/pins_arduino.c $(ARDUINO)/wiring.c \ +$(ARDUINO)/wiring_analog.c $(ARDUINO)/wiring_digital.c \ +$(ARDUINO)/wiring_pulse.c $(ARDUINO)/wiring_serial.c \ +$(ARDUINO)/wiring_shift.c $(ARDUINO)/WInterrupts.c +CXXSRC = $(ARDUINO)/HardwareSerial.cpp $(SOFTWARE_SERIAL)/SoftwareSerial.cpp<%= params['libraries'].collect{|l| " $(LIBRARY_ROOT)/#{ l }/#{l }.cpp"}.join('') %> +MCU = <%= params['mcu'] %> +<% if params['asm_files'] %>ASRC = <%= params['asm_files'].join(' ') %><% end %> +F_CPU = 16000000 +FORMAT = ihex +UPLOAD_RATE = 19200 +BIN_DIR = <%= params['arduino_root'] %>/hardware/tools/avr/bin + +# Name of this Makefile (used for "make depend"). +MAKEFILE = Makefile + +# Debugging format. +# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2. +# AVR (extended) COFF requires stabs, plus an avr-objcopy run. +DEBUG = stabs + +OPT = s + +# Place -D or -U options here +CDEFS = -DF_CPU=$(F_CPU) +CXXDEFS = -DF_CPU=$(F_CPU) + +# Place -I options here +CINCS = -I$(ARDUINO) -I$(SOFTWARE_SERIAL)<% params['libraries'].each do |l| %> -I$(LIBRARY_ROOT)/<%= l %><% end %> ++CXXINCS = -I$(ARDUINO) -I$(SOFTWARE_SERIAL)<% params['libraries'].each do |l| %> -I$(LIBRARY_ROOT)/<%= l %><% end %> + +# Compiler flag to set the C Standard level. +# c89 - "ANSI" C +# gnu89 - c89 plus GCC extensions +# c99 - ISO C99 standard (not yet fully implemented) +# gnu99 - c99 plus GCC extensions +CSTANDARD = -std=gnu99 +CDEBUG = -g$(DEBUG) +CWARN = -Wall -Wstrict-prototypes +CTUNING = -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums +#CEXTRA = -Wa,-adhlns=$(<:.c=.lst) + +CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CEXTRA) +CXXFLAGS = $(CDEFS) $(CINCS) -O$(OPT) +#ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs +LDFLAGS = -lm + + +# Programming support using avrdude. Settings and variables. +AVRDUDE_PROGRAMMER = stk500 +AVRDUDE_PORT = $(PORT) +AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex +AVRDUDE_FLAGS = -F -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) \ + -b $(UPLOAD_RATE) -C <%= params['arduino_root'] %>/hardware/tools/avr/etc/avrdude.conf + +# Program settings +CC = $(BIN_DIR)/avr-gcc +CXX = $(BIN_DIR)/avr-g++ +OBJCOPY = $(BIN_DIR)/avr-objcopy +OBJDUMP = $(BIN_DIR)/avr-objdump +AR = $(BIN_DIR)/avr-ar +SIZE = $(BIN_DIR)/avr-size +NM = $(BIN_DIR)/avr-nm +AVRDUDE = $(BIN_DIR)/avrdude +REMOVE = rm -f +MV = mv -f + +# Define all object files. +OBJ = $(SRC:.c=.o) $(CXXSRC:.cpp=.o) $(ASRC:.S=.o) + +# Define all listing files. +LST = $(ASRC:.S=.lst) $(CXXSRC:.cpp=.lst) $(SRC:.c=.lst) + +# Combine all necessary flags and optional flags. +# Add target processor to flags. +ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) +ALL_CXXFLAGS = -mmcu=$(MCU) -I. $(CXXFLAGS) +ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) + + +# Default target. +all: build + +build: elf hex + +elf: $(TARGET).elf +hex: $(TARGET).hex +eep: $(TARGET).eep +lss: $(TARGET).lss +sym: $(TARGET).sym + +# Program the device. +upload: $(TARGET).hex + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) + + + + +# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB. +COFFCONVERT=$(OBJCOPY) --debugging \ +--change-section-address .data-0x800000 \ +--change-section-address .bss-0x800000 \ +--change-section-address .noinit-0x800000 \ +--change-section-address .eeprom-0x810000 + + +coff: $(TARGET).elf + $(COFFCONVERT) -O coff-avr $(TARGET).elf $(TARGET).cof + + +extcoff: $(TARGET).elf + $(COFFCONVERT) -O coff-ext-avr $(TARGET).elf $(TARGET).cof + + +.SUFFIXES: .elf .hex .eep .lss .sym + +.elf.hex: + $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ + +.elf.eep: + -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ + --change-section-lma .eeprom=0 -O $(FORMAT) $< $@ + +# Create extended listing file from ELF output file. +.elf.lss: + $(OBJDUMP) -h -S $< > $@ + +# Create a symbol table from ELF output file. +.elf.sym: + $(NM) -n $< > $@ + + +core.a: $(OBJ) + @for i in $(OBJ); do echo $(AR) rcs core.a $$i; $(AR) rcs core.a $$i; done + +# Link: create ELF output file from library. +$(TARGET).elf: core.a + $(CC) $(ALL_CFLAGS) -o $@ $(TARGET).cpp -L. core.a $(LDFLAGS) + +# Compile: create object files from C++ source files. +.cpp.o: + $(CXX) -c $(ALL_CXXFLAGS) $< -o $@ + +# Compile: create object files from C source files. +.c.o: + $(CC) -c $(ALL_CFLAGS) $< -o $@ + + +# Compile: create assembler files from C source files. +.c.s: + $(CC) -S $(ALL_CFLAGS) $< -o $@ + + +# Assemble: create object files from assembler source files. +.S.o: + $(CC) -c $(ALL_ASFLAGS) $< -o $@ + + + +# Target: clean project. +clean: + $(REMOVE) $(TARGET).hex $(TARGET).eep $(TARGET).cof $(TARGET).elf \ + $(TARGET).map $(TARGET).sym $(TARGET).lss core.a \ + $(OBJ) $(LST) $(SRC:.c=.s) $(SRC:.c=.d) $(CXXSRC:.cpp=.s) $(CXXSRC:.cpp=.d) + +depend: + if grep '^# DO NOT DELETE' $(MAKEFILE) >/dev/null; \ + then \ + sed -e '/^# DO NOT DELETE/,$$d' $(MAKEFILE) > \ + $(MAKEFILE).$$$$ && \ + $(MV) $(MAKEFILE).$$$$ $(MAKEFILE); \ + fi + echo '# DO NOT DELETE THIS LINE -- make depend depends on it.' \ + >> $(MAKEFILE); \ + $(CC) -M -mmcu=$(MCU) $(CDEFS) $(CINCS) $(SRC) $(ASRC) >> $(MAKEFILE) + +.PHONY: all build elf hex eep lss sym program coff extcoff clean depend diff --git a/lib/rad/generators/makefile/makefile.rb b/lib/rad/generators/makefile/makefile.rb new file mode 100644 index 0000000..9c6a0cf --- /dev/null +++ b/lib/rad/generators/makefile/makefile.rb @@ -0,0 +1,39 @@ +require 'erb' +require 'yaml' + +class Makefile + class << self + + # build the sketch Makefile for the given template based on the values in its software and hardware config files + def compose_for_sketch(sketch_name) + params = hardware_params.merge software_params + params['target'] = sketch_name + + params['libraries_root'] = "#{File.expand_path(RAD_ROOT)}/vendor/libraries" + params['libraries'] = libraries + + params['asm_files'] = Dir.entries( File.expand_path(RAD_ROOT) + "/" + PROJECT_DIR_NAME ).select{|e| e =~ /\.S/} + + e = ERB.new File.read("#{File.dirname(__FILE__)}/makefile.erb") + + File.open("#{RAD_ROOT}/#{sketch_name}/Makefile", "w") do |f| + f << e.result(binding) + end + end + + def libraries + Dir.entries("#{RAD_ROOT}/vendor/libraries").select{|e| !(e =~ /\./)} + end + + def hardware_params + return @hardware_params if @hardware_params + return @hardware_params = YAML.load_file( "#{RAD_ROOT}/config/hardware.yml") + end + + def software_params + return @software_params if @software_params + return @software_params = YAML.load_file( "#{RAD_ROOT}/config/software.yml" ) + end + + end +end \ No newline at end of file diff --git a/lib/rad/init.rb b/lib/rad/init.rb new file mode 100644 index 0000000..44b9431 --- /dev/null +++ b/lib/rad/init.rb @@ -0,0 +1,12 @@ +RAD_ROOT = "#{File.dirname(__FILE__)}/../.." unless defined?(RAD_ROOT) + +unless defined?(PROJECT_DIR_NAME) + a = File.expand_path(File.expand_path("#{RAD_ROOT}")).split("/") + PROJECT_DIR_NAME = a[a.length-1] +end + + +%w(generators/makefile/makefile.rb arduino_sketch.rb tasks/rad.rb).each do |path| + require File.expand_path("#{RAD_ROOT}/vendor/rad/#{path}") +end + diff --git a/lib/rad/sim/arduino_sketch.rb b/lib/rad/sim/arduino_sketch.rb new file mode 100644 index 0000000..307cdaa --- /dev/null +++ b/lib/rad/sim/arduino_sketch.rb @@ -0,0 +1,57 @@ +ON = true +OFF = !ON +HIGH = ON +LOW = !HIGH + +class ArduinoSketch + attr_accessor :pins + + def initialize + @pins = self.class.instance_variable_get("@pins") + end + + def self.output_pin(num, opts) + module_eval "@pins ||= []" + module_eval do + @pins << Pin.new( num, :type => :output ) + end + + if opts[:as] + module_eval <<-CODE + def #{opts[:as]} + pins.select{|p| p.num == #{num}}.first + end + CODE + end + end + + def loop + end + + def digitalWrite( pin, value ) + to_change = pins.select{|p| p.num == pin.num}.first + to_change.value = value + end + + def delay( millis ) + end + + # def serial_read + # end + + # def serial_available + # end + + # def blink + # end +end + +class Pin + attr_accessor :num, :type, :value + + def initialize num, opts + @num = num + @type = opts[:type] + @value = opts[:value] || false + end +end diff --git a/lib/rad/sim/arduino_sketch.rb~ b/lib/rad/sim/arduino_sketch.rb~ new file mode 100644 index 0000000..d81039b --- /dev/null +++ b/lib/rad/sim/arduino_sketch.rb~ @@ -0,0 +1,10 @@ +class ArduinoSketch + def class.output_pin(num, opts) + end + + def loop + end + + def blink + end +end diff --git a/lib/rad/tasks/build_and_make.rake b/lib/rad/tasks/build_and_make.rake new file mode 100644 index 0000000..8c7dcdc --- /dev/null +++ b/lib/rad/tasks/build_and_make.rake @@ -0,0 +1,88 @@ +require File.expand_path(File.dirname(__FILE__) + "/../init.rb") +require 'ruby_to_ansi_c' + +namespace :make do + + desc "compile the sketch and then upload it to your Arduino board" + task :upload => :compile do + if Makefile.hardware_params['physical_reset'] + puts "Reset the Arduino and hit enter.\n==If your board doesn't need it, you can turn off this prompt in config/software.yml==" + STDIN.gets.chomp + end + sh %{cd #{RAD_ROOT}/#{@sketch_name}; make upload} + end + + desc "generate a makefile and use it to compile the .cpp" + task :compile => [:clean_sketch_dir, "build:sketch"] do # should also depend on "build:sketch" + Makefile.compose_for_sketch( @sketch_name ) + # not allowed? sh %{export PATH=#{Makefile.software_params[:arduino_root]}/tools/avr/bin:$PATH} + sh %{cd #{RAD_ROOT}/#{@sketch_name}; make depend; make} + end + + task :clean_sketch_dir => ["build:file_list", "build:sketch_dir"] do + @sketch_name = @file_names.first.split(".").first + + FileList.new(Dir.entries("#{RAD_ROOT}/#{@sketch_name}")).exclude("#{@sketch_name}.cpp").exclude(/^\./).each do |f| + sh %{rm #{RAD_ROOT}/#{@sketch_name}/#{f}} + end + end +end + +namespace :build do + + desc "actually build the sketch" + task :sketch => [:file_list, :sketch_dir, :setup] do + klass = @file_names.first.split(".").first.split("_").collect{|c| c.capitalize}.join("") + eval ArduinoSketch.pre_process(File.read(@file_names.first)) + @loop = RubyToAnsiC.translate(constantize(klass), "loop") + result = "#{@setup}\n#{@loop}\n" + name = @file_names.first.split(".").first + File.open("#{name}/#{name}.cpp", "w"){|f| f << result} + end + + # needs to write the library include and the method signatures + desc "build setup function" + task :setup do + klass = @file_names.first.split(".").first.split("_").collect{|c| c.capitalize}.join("") + eval "class #{klass} < ArduinoSketch; end;" + + @@as = ArduinoSketch.new + + delegate_methods = @@as.methods - Object.new.methods + delegate_methods.reject!{|m| m == "compose_setup"} + + delegate_methods.each do |meth| + constantize(klass).module_eval <<-CODE + def self.#{meth}(*args) + @@as.#{meth}(*args) + end + CODE + end + + eval File.read(@file_names.first) + @setup = @@as.compose_setup + end + + desc "setup target directory named after your sketch class" + task :sketch_dir => [:file_list] do + mkdir_p "#{@file_names.first.split(".").first}" + end + + task :file_list do + @file_names = [] + Dir.entries( File.expand_path(RAD_ROOT) ).each do |f| + if (f =~ /\.rb$/) + @file_names << f + end + end + end +end + +#yoinked from Rails +def constantize(camel_cased_word) + unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word + raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!" + end + + Object.module_eval("::#{$1}", __FILE__, __LINE__) +end \ No newline at end of file diff --git a/lib/rad/tasks/rad.rb b/lib/rad/tasks/rad.rb new file mode 100644 index 0000000..66f27fa --- /dev/null +++ b/lib/rad/tasks/rad.rb @@ -0,0 +1,2 @@ +require 'rake' +Dir["#{File.dirname(__FILE__)}/*.rake"].each { |ext| load ext } \ No newline at end of file diff --git a/lib/rad/todo.txt b/lib/rad/todo.txt new file mode 100644 index 0000000..d838118 --- /dev/null +++ b/lib/rad/todo.txt @@ -0,0 +1,13 @@ +TODO: +===== + +Future: + - complete library system: script/install library some_library + - bin/rad: + - setup and use a ~/.rad for new project defaults + - put repository on git + - project gallery (examples with movies) + - testing framework + - implement wire lib + - subclasses of ArduinoSketch (should just work, but what are they for?) + - simulator \ No newline at end of file diff --git a/lib/rad/version.rb b/lib/rad/version.rb new file mode 100644 index 0000000..fa10d05 --- /dev/null +++ b/lib/rad/version.rb @@ -0,0 +1,9 @@ +module Rad #:nodoc: + module VERSION #:nodoc: + MAJOR = 0 + MINOR = 2 + TINY = 2 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/pkg/rad-0.2.2.gem b/pkg/rad-0.2.2.gem new file mode 100644 index 0000000000000000000000000000000000000000..f0420fe5c4b93eb7b41ed31422e93b876b547f60 GIT binary patch literal 48128 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%0A!y`nX{`}s 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 true parameter to input_pin +* 2 minor enhancements: + * cleanups in makefile.rb + * serial_print_str method added; can now send strings over the serial port + +== 0.0.4 2007-07-24 + +* 1 major enhancement: + * serial_begin class method + +== 0.0.3 2007-07-23 + +* 1 major enhancement: + * bug fix in blink helper method + +== 0.0.2 2007-07-23 + +* 1 major enhancement: + * blink helper method + +== 0.0.1 2007-07-22 + +* 1 major enhancement: + * Initial release + * most features of the Arduino software library functional + * experimental implementation of the serial interface + * complete rake-based build, compile, and upload cycle + + diff --git a/pkg/rad-0.2.2/License.txt b/pkg/rad-0.2.2/License.txt new file mode 100644 index 0000000..8604702 --- /dev/null +++ b/pkg/rad-0.2.2/License.txt @@ -0,0 +1,282 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + + diff --git a/pkg/rad-0.2.2/Manifest.txt b/pkg/rad-0.2.2/Manifest.txt new file mode 100644 index 0000000..56beafc --- /dev/null +++ b/pkg/rad-0.2.2/Manifest.txt @@ -0,0 +1,32 @@ +History.txt +License.txt +Manifest.txt +README.txt +Rakefile +bin/rad +lib/libraries/SWSerLCDpa/keywords.txt +lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp +lib/libraries/SWSerLCDpa/SWSerLCDpa.h +lib/rad.rb +lib/rad/arduino_sketch.rb +lib/rad/init.rb +lib/rad/todo.txt +lib/rad/tasks/build_and_make.rake +lib/rad/tasks/rad.rb +lib/rad/version.rb +lib/rad/generators/makefile/makefile.erb +lib/rad/generators/makefile/makefile.rb +scripts/txt2html +setup.rb +spec/models/spec_helper.rb +spec/models/arduino_sketch_spec.rb +spec/spec.opts +website/index.html +website/index.txt +website/javascripts/rounded_corners_lite.inc.js +website/stylesheets/screen.css +website/template.rhtml +website/examples/assembler_test.rb.html +website/examples/gps_reader.rb.html +website/examples/hello_world.rb.html +website/examples/serial_motor.rb.html diff --git a/pkg/rad-0.2.2/README.txt b/pkg/rad-0.2.2/README.txt new file mode 100644 index 0000000..ff55fab --- /dev/null +++ b/pkg/rad-0.2.2/README.txt @@ -0,0 +1,43 @@ +=Welcome to RAD (Ruby Arduino Development) + +RAD is a framework for programming the Arduino physcial computing platform using Ruby. RAD converts Ruby scripts written using a set of Rails-like conventions and helpers into C source code which can be compiled and run on the Arduino microcontroller. It also provides a set of Rake tasks for automating the compilation and upload process. + +For a full introduction see http://rad.rubyforge.org + +==Documentation + +The main documentation is here: ArduinoSketch. + +See also the Arduino Software reference: http://www.arduino.cc/en/Reference/HomePage + +==Examples + +See the examples directory for lots of examples of RAD in action: http://rad.rubyforge.org/examples + +==Getting Started + +Install the gem: + +$ sudo gem install rad + +Run the rad command to create a new project: + +$ rad my_project + +Write a sketch that will blink a single LED every 500ms: + + class MyProject < ArduinoSketch + output_pin 13, :as => led + + def loop + blink led, 500 + end + end + +Attach your Arduino and use rake to complile and upload your sketch: + +$ rake make:upload + +==Get Involved + +Cheers? Jeers? Wanna help out? Contact Greg Borenstein: greg [dot] borenstein [at] gmail [dot] com \ No newline at end of file diff --git a/pkg/rad-0.2.2/Rakefile b/pkg/rad-0.2.2/Rakefile new file mode 100644 index 0000000..00e65bb --- /dev/null +++ b/pkg/rad-0.2.2/Rakefile @@ -0,0 +1,139 @@ +require 'rubygems' +require 'rake' +require 'rake/clean' +require 'rake/testtask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/rdoctask' +require 'rake/contrib/rubyforgepublisher' +require 'fileutils' +require 'hoe' +begin + require 'spec/rake/spectask' +rescue LoadError + puts 'To use rspec for testing you must install rspec gem:' + puts '$ sudo gem install rspec' + exit +end + +include FileUtils +require File.join(File.dirname(__FILE__), 'lib', 'rad', 'version') + +BIN = "rad" +AUTHOR = 'Greg Borenstein' # can also be an array of Authors +EMAIL = "greg@mfdz.com" +DESCRIPTION = "A framework for programming the Arduino physcial computing platform using Ruby. RAD converts Ruby scripts written using a set of Rails-like conventions and helpers into C source code which can be compiled and run on the Arduino microcontroller." +GEM_NAME = 'rad' # what ppl will type to install your gem + +@config_file = "~/.rubyforge/user-config.yml" +@config = nil +def rubyforge_username + unless @config + begin + @config = YAML.load(File.read(File.expand_path(@config_file))) + rescue + puts <<-EOS +ERROR: No rubyforge config file found: #{@config_file}" +Run 'rubyforge setup' to prepare your env for access to Rubyforge + - See http://newgem.rubyforge.org/rubyforge.html for more details + EOS + exit + end + end + @rubyforge_username ||= @config["username"] +end + +RUBYFORGE_PROJECT = 'rad' # The unix name for your project +HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org" +DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}" + +NAME = "rad" +REV = nil +# UNCOMMENT IF REQUIRED: +# REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil +VERS = Rad::VERSION::STRING + (REV ? ".#{REV}" : "") +CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] +RDOC_OPTS = ['--quiet', '--title', 'rad documentation', + "--opname", "index.html", + "--line-numbers", + "--main", "README", + "--inline-source"] + +class Hoe + def extra_deps + @extra_deps.reject { |x| Array(x).first == 'hoe' } + end +end + +# Generate all the Rake tasks +# Run 'rake -T' to see list of generated tasks (from gem root directory) +hoe = Hoe.new(GEM_NAME, VERS) do |p| + p.author = AUTHOR + p.description = DESCRIPTION + p.email = EMAIL + p.summary = DESCRIPTION + p.url = HOMEPATH + p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT + p.test_globs = ["test/**/test_*.rb"] + p.clean_globs |= CLEAN #An array of file patterns to delete on clean. + + # == Optional + p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n") + p.extra_deps = [ ['RubyToC', '>= 1.0.0'] ] + #p.spec_extras = {} # A hash of extra values to set in the gemspec. +end + +CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n") +PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}" +hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc') + +desc 'Generate website files' +task :website_generate do + Dir['website/**/*.txt'].each do |txt| + sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} } + end +end + +desc 'Upload website files to rubyforge' +task :website_upload do + host = "#{rubyforge_username}@rubyforge.org" + remote_dir = "/var/www/gforge-projects/#{PATH}/" + local_dir = 'website' + sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}} +end + +desc 'Generate and upload website files' +task :website => [:website_generate, :website_upload, :publish_docs] + +desc 'Release the website and new gem version' +task :deploy => [:check_version, :website, :release] do + puts "Remember to create SVN tag:" + puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " + + "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} " + puts "Suggested comment:" + puts "Tagging release #{CHANGES}" +end + +desc 'Runs tasks website_generate and install_gem as a local deployment of the gem' +task :local_deploy => [:website_generate, :install_gem] + +task :check_version do + unless ENV['VERSION'] + puts 'Must pass a VERSION=x.y.z release version' + exit + end + unless ENV['VERSION'] == VERS + puts "Please update your version.rb to match the release version, currently #{VERS}" + exit + end +end + +desc "Run the specs under spec/models" +Spec::Rake::SpecTask.new do |t| + t.spec_opts = ['--options', "spec/spec.opts"] + t.spec_files = FileList['spec/models/*_spec.rb'] +end + +desc "Default task is to run specs" +task :default => :spec + diff --git a/pkg/rad-0.2.2/bin/rad b/pkg/rad-0.2.2/bin/rad new file mode 100644 index 0000000..d44ba3c --- /dev/null +++ b/pkg/rad-0.2.2/bin/rad @@ -0,0 +1,142 @@ +#!/usr/bin/env ruby + +begin + require 'rubygems' +rescue LoadError + # no rubygems to load, so we fail silently +end + +require 'optparse' +require 'fileutils' +require 'yaml' + + + +class OptionParser #:nodoc: + + def self.parse(args) + # defaults + + options = {"hardware" => { + "serial_port" => '/dev/tty.usbserial*', + "mcu" => "atmega168", + "physical_reset" => false + }, + "software" => { + "arduino_root" => "/Applications/arduino-0011" + } + } + + opts = OptionParser.new do |opts| + + opts.banner = <<-BANNER +Build a directory for your RAD Project and install RAD in its vendor sub-directory. + +Usage: #{File.basename($0)} + BANNER + + opts.on("-p", "--port [SERIAL PORT]", + "Path to your serial port", + " (default: #{options['hardware']['serial_port']})") do |port| + + options["hardware"]["serial_port"] = port if port + end + + opts.on("-m", "--mcu [PROCESSOR TYPE]", + "Type of processor on your board", + " (default: #{options['hardware']['mcu']})") do |port| + + options["hardware"]["serial_port"] = port if port + end + + opts.on("-r", "--reset [RESET REQUIRED]", + "Require a hardware reset before uploading?", + " (default: #{options['hardware']['physical_reset']})") do |no_reset| + options["hardware"]["physical_reset"] = true unless no_reset + end + + opts.on("-a", "--arduino [ARDUINO DIR]", + "Path to your Arduino install", + " (default: #{options['software']['arduino_root']})") do |arduino| + options["software"]["arduino_root"] = arduino if arduino + end + + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end + end + + opts.parse!(args) + [options, opts] + end + +end + + +# home = ENV['HOME'] || ENV['USERPROFILE'] || ENV['HOMEPATH'] +# begin +# config = YAML::load open(home + "/.rad") +# rescue +# config = false +# end +# + + +# Handle options: +options, parser = OptionParser.parse(ARGV) +sketch_name = ARGV[0] +parser.parse!(["-h"]) unless sketch_name + + +# Build vendor/rad: + +FileUtils.mkdir_p "#{sketch_name}/vendor/rad" +puts "Successfully created your sketch directory." + +FileUtils.cp_r "#{File.dirname(__FILE__)}/../lib/rad/.", "#{sketch_name}/vendor/rad" +puts "Installed RAD into #{sketch_name}/vendor/rad" +puts + +# Build vendor/libraries: + +FileUtils.mkdir_p "#{sketch_name}/vendor/libraries" +puts "Successfully created your libraries directory." + +FileUtils.cp_r "#{File.dirname(__FILE__)}/../lib/libraries/SWSerLCDpa/.", "#{sketch_name}/vendor/libraries/SWSerLCDpa" +puts "Installed SWSerLCDpa into #{sketch_name}/vendor/libraries" +puts + +# Build sketch files, etc.: + +FileUtils.touch "#{sketch_name}/#{sketch_name}.rb" +File.open("#{sketch_name}/#{sketch_name}.rb", "w") do |file| + file << <<-EOS +class #{sketch_name.split("_").collect{|c| c.capitalize}.join("")} < ArduinoSketch +end + EOS +end +puts "Added #{sketch_name}/#{sketch_name}.rb" + +File.open("#{sketch_name}/Rakefile", 'w') do |file| + file << <<-EOS +require 'vendor/rad/init.rb' + EOS +end +puts "Added #{sketch_name}/Rakefile" + +FileUtils.mkdir_p "#{sketch_name}/config" +puts "Added #{sketch_name}/config" + +File.open("#{sketch_name}/config/hardware.yml", 'w') do |file| + file << options["hardware"].to_yaml +end +puts "Added #{sketch_name}/config/hardware.yml" + +File.open("#{sketch_name}/config/software.yml", 'w') do |file| + file << options["software"].to_yaml +end +puts "Added #{sketch_name}/config/software.yml" + +puts +puts "Run 'rake -T' inside your sketch dir to learn how to compile and upload it." \ No newline at end of file diff --git a/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp b/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp new file mode 100755 index 0000000..0610de9 --- /dev/null +++ b/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/SWSerLCDpa.cpp @@ -0,0 +1,250 @@ +/* + SWSerLCDpa.cpp - Software serial to Peter Anderson controller chip based + LCD display library Adapted from SoftwareSerial.cpp (c) 2006 David A. Mellis + by Brian B. Riley, Underhill Center, Vermont, USA, July 2007 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/****************************************************************************** + * Includes + ******************************************************************************/ + +#include "WConstants.h" +#include "SWSerLCDpa.h" + +/****************************************************************************** + * Definitions + ******************************************************************************/ + +/****************************************************************************** + * Constructors + ******************************************************************************/ + +SWSerLCDpa::SWSerLCDpa(uint8_t transmitPin) +{ + _transmitPin = transmitPin; + _baudRate = 0; +} + +/****************************************************************************** + * User API + ******************************************************************************/ + +void SWSerLCDpa::begin(long speed) +{ + _baudRate = speed; + _bitPeriod = 1000000 / _baudRate; + + digitalWrite(_transmitPin, HIGH); + delayMicroseconds( _bitPeriod); // if we were low this establishes the end +} + +void SWSerLCDpa::print(uint8_t b) +{ + if (_baudRate == 0) + return; + + int bitDelay = _bitPeriod - clockCyclesToMicroseconds(50); // a digitalWrite is about 50 cycles + byte mask; + + digitalWrite(_transmitPin, LOW); + delayMicroseconds(bitDelay); + + for (mask = 0x01; mask; mask <<= 1) { + if (b & mask){ // choose bit + digitalWrite(_transmitPin,HIGH); // send 1 + } + else{ + digitalWrite(_transmitPin,LOW); // send 1 + } + delayMicroseconds(bitDelay); + } + + digitalWrite(_transmitPin, HIGH); + delayMicroseconds(bitDelay); +} + +void SWSerLCDpa::print(const char *s) +{ + while (*s) { + print(*s++); + delay(1); + } +} + +void SWSerLCDpa::print(char c) +{ + print((uint8_t) c); +} + +void SWSerLCDpa::print(int n) +{ + print((long) n); +} + +void SWSerLCDpa::print(unsigned int n) +{ + print((unsigned long) n); +} + +void SWSerLCDpa::print(long n) +{ + if (n < 0) { + print('-'); + n = -n; + } + printNumber(n, 10); +} + +void SWSerLCDpa::print(unsigned long n) +{ + printNumber(n, 10); +} + +void SWSerLCDpa::print(long n, int base) +{ + if (base == 0) + print((char) n); + else if (base == 10) + print(n); + else + printNumber(n, base); +} + +// -------- PHA unique codes ------------------------- +void SWSerLCDpa::println(void) +{ + print("?n"); + delay(10); +} + +void SWSerLCDpa::clearscr(void) +{ + print("?f"); + delay(100); +} + +void SWSerLCDpa::home(void) +{ + print("?a"); + delay(10); +} + + +void SWSerLCDpa::setgeo(int geometry) +{ + print("?G"); + print(geometry); + delay(200); +} + +void SWSerLCDpa::setintensity(int intensity) +{ + print("?B"); + if (intensity < 16) + print('0'); + print(intensity, 16); + delay(200); +} + +void SWSerLCDpa::intoBignum(void) +{ + print("?>3"); +} + +void SWSerLCDpa::outofBignum(void) +{ + print("?<"); +} + + +void SWSerLCDpa::setxy(int x, int y) +{ + print("?y"); + print(y); + print("?x"); + if (x < 10) + print('0'); + print(x); + delay(10); +} + + +void SWSerLCDpa::println(char c) +{ + print(c); + println(); +} + +void SWSerLCDpa::println(const char c[]) +{ + print(c); + println(); +} + +void SWSerLCDpa::println(uint8_t b) +{ + print(b); + println(); +} + +void SWSerLCDpa::println(int n) +{ + print(n); + println(); +} + +void SWSerLCDpa::println(long n) +{ + print(n); + println(); +} + +void SWSerLCDpa::println(unsigned long n) +{ + print(n); + println(); +} + +void SWSerLCDpa::println(long n, int base) +{ + print(n, base); + println(); +} + +// Private Methods ///////////////////////////////////////////////////////////// + +void SWSerLCDpa::printNumber(unsigned long n, uint8_t base) +{ + unsigned char buf[8 * sizeof(long)]; // Assumes 8-bit chars. + unsigned long i = 0; + + if (n == 0) { + print('0'); + return; + } + + while (n > 0) { + buf[i++] = n % base; + n /= base; + } + + for (; i > 0; i--) + print((char) (buf[i - 1] < 10 ? '0' + buf[i - 1] : 'A' + buf[i - 1] - 10)); + + delay(8); + +} diff --git a/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/SWSerLCDpa.h b/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/SWSerLCDpa.h new file mode 100755 index 0000000..517be49 --- /dev/null +++ b/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/SWSerLCDpa.h @@ -0,0 +1,62 @@ +/* + SWSerLCDpa.h - Software serial to Peter Anderson controller chip based + LCD display library Adapted from SoftwareSerial.cpp (c) 2006 David A. Mellis + by Brian B. Riley, Underhill Center, Vermont, USA, July 2007 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef SWSerLCDpa_h +#define SWSerLCDpa_h + +#include + +class SWSerLCDpa +{ + private: + uint8_t _transmitPin; + long _baudRate; + int _bitPeriod; + void printNumber(unsigned long, uint8_t); + public: + SWSerLCDpa(uint8_t); + void begin(long); + void print(char); + void print(const char[]); + void print(uint8_t); + void print(int); + void print(unsigned int); + void print(long); + void print(unsigned long); + void print(long, int); + void println(void); + void clearscr(void); + void home(void); + void setgeo(int); + void setintensity(int); + void intoBignum(void); + void outofBignum(void); + void setxy(int, int); + void println(char); + void println(const char[]); + void println(uint8_t); + void println(int); + void println(long); + void println(unsigned long); + void println(long, int); +}; + +#endif + diff --git a/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/keywords.txt b/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/keywords.txt new file mode 100644 index 0000000..de5a74c --- /dev/null +++ b/pkg/rad-0.2.2/lib/libraries/SWSerLCDpa/keywords.txt @@ -0,0 +1,18 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SoftwareSerial KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/pkg/rad-0.2.2/lib/rad.rb b/pkg/rad-0.2.2/lib/rad.rb new file mode 100644 index 0000000..d8a1617 --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad.rb @@ -0,0 +1,5 @@ +module Rad +end + +require 'rad/version' +require 'rad/init' \ No newline at end of file diff --git a/pkg/rad-0.2.2/lib/rad/arduino_sketch.rb b/pkg/rad-0.2.2/lib/rad/arduino_sketch.rb new file mode 100644 index 0000000..a22cb03 --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad/arduino_sketch.rb @@ -0,0 +1,533 @@ +# ArduinoSketch is the main access point for working with RAD. Sub-classes of ArduinoSketch have access to a wide array of convenient class methods (documented below) for doing the most common kinds of setup needed when programming the Arduino such as configuring input and output pins and establishing serial connections. Here is the canonical 'hello world' example of blinking a single LED in RAD: +# +# class HelloWorld < ArduinoSketch +# output_pin 13, :as => led +# +# def loop +# blink 13, 500 +# end +# end +# +# As you can see from this example, your ArduinoSketch sub-class can be dividied into two-parts: class methods for doing configuration and a loop method which will be run repeatedly at the Arduino's clock rate. Documentation for the various available class methods is below. The ArduinoSketch base class is designed to work with a series of rake tasks to automatically translate your loop method into C++ for compilation by the Arduino toolchain (see link://files/lib/rad/tasks/build_and_make_rake.html for details). See http://rad.rubyforge.org/examples for lots more examples of usage. +# +# ==Arduino built-in methods +# Thanks to this translation process you can take advantage of the complete Arduino software API (full docs here: http://www.arduino.cc/en/Reference/HomePage). What follows is the core of a RAD-Arduino dictionary for translating between RAD methods and the Arduino functionality they invoke, N.B. many Arduino methods have been left out (including the libraries for Time, Math, and Random Numbers, as the translation between them and their RAD counterparts should be relatively straightforward after perusing the examples here). For further details on each method, visit their Arduino documenation. +# +# Digital I/O +# +# digital_write(pin, value) +# +# Arduino method: digitalWrite(pin, value) +# +# Description: "Ouputs either HIGH or LOW at a specified pin." +# +# Documentation: http://www.arduino.cc/en/Reference/DigitalWrite +# +# digital_read(pin) +# +# Arduino method: digitalRead(pin) +# +# Description: "Reads the value from a specified pin, it will be either HIGH or LOW." +# +# Documentation: http://www.arduino.cc/en/Reference/DigitalRead +# +# Analog I/O +# +# analog_read(pin) +# +# Arduino method: analogRead(pin) +# +# Description: "Reads the value from the specified analog pin. The Arduino board contains a 6 channel +# (8 channels on the Mini), 10-bit analog to digital converter. This means that it will map input +# voltages between 0 and 5 volts into integer values between 0 and 1023. This yields a resolution +# between readings of: 5 volts / 1024 units or, .0049 volts (4.9 mV) per unit. It takes about 100 +# us (0.0001 s) to read an analog input, so the maximum reading rate is about 10,000 times a second." +# +# Documentation: http://www.arduino.cc/en/Reference/AnalogRead +# +# analog_write(pin, value) +# +# Arduino method: analogWrite(pin, value) +# +# Description: "Writes an analog value (PWM wave) to a pin. On newer Arduino boards (including the Mini +# and BT) with the ATmega168 chip, this function works on pins 3, 5, 6, 9, 10, and 11. Older USB and +# serial Arduino boards with an ATmega8 only support analogWrite() on pins 9, 10, and 11. Can be used +# to light a LED at varying brightnesses or drive a motor at various speeds. After a call to analogWrite, +# the pin will generate a steady wave until the next call to analogWrite (or a call to digitalRead or +# digitalWrite on the same pin)." +# +# Documentation: http://www.arduino.cc/en/Reference/AnalogWrite +# +# Serial Communication +# +# serial_available() +# +# Arduino method: Serial.available() +# +# Description: "Get the number of bytes (characters) available for reading over the serial port. +# Returns the number of bytes available to read in the serial buffer, or 0 if none are +# available. If any data has come in, Serial.available() will be greater than 0. The serial buffer +# can hold up to 128 bytes." +# +# Documentation: http://www.arduino.cc/en/Serial/Available +# +# serial_read() +# +# Arduino method: Serial.read() +# +# Description: "Reads incoming serial data and returns the first byte of incoming serial data +# available (or -1 if no data is available)" +# +# Documentation: http://www.arduino.cc/en/Serial/Read +# +# serial_print(data) +# +# Arduino method: Serial.print(data) +# +# Description: "Prints data to the serial port." +# +# Documentation: http://www.arduino.cc/en/Serial/Print +# +# serial_println(data) +# +# Arduino method: Serial.println(data) +# +# Description: "Prints a data to the serial port, followed by a carriage return character +# (ASCII 13, or '\r') and a newline character (ASCII 10, or '\n'). This command takes the +# same forms as Serial.print():" +# +# Documentation: http://www.arduino.cc/en/Serial/Println +# +# serial_flush() +# +# Arduino method: Serial.flush() +# +# Description: "Flushes the buffer of incoming serial data. That is, any call to Serial.read() +# or Serial.available() will return only data received after the most recent call +# to Serial.flush()." +# +# Documentation: http://www.arduino.cc/en/Serial/Flush +# +class ArduinoSketch + + def initialize #:nodoc: + @declarations = [] + @pin_modes = {:output => [], :input => []} + @pullups = [] + @other_setup = [] # specifically, Serial.begin + @assembler_declarations = [] + @accessors = [] + @signatures = ["void blink();", "int main();"] + + helper_methods = [] + helper_methods << "void blink(int pin, int ms) {" + helper_methods << "\tdigitalWrite( pin, HIGH );" + helper_methods << "\tdelay( ms );" + helper_methods << "\tdigitalWrite( pin, LOW );" + helper_methods << "\tdelay( ms );" + helper_methods << "}" + @helper_methods = helper_methods.join( "\n" ) + end + + # Setup variables outside of the loop. Does some magic based on type of arguments. Subject to renaming. Use with caution. + def vars(opts={}) + opts.each do |k,v| + if v.class == Symbol + @declarations << "#{v} _#{k};" + @accessors << <<-CODE + #{v} #{k}(){ + \treturn _#{k}; + } + CODE + else + type = case v + when Integer + "int" + when String + "char*" + when TrueClass + "bool" + when FalseClass + "bool" + end + + @declarations << "#{type} _#{k} = #{v};" + @accessors << <<-CODE + #{type} #{k}(){ + \treturn _#{k}; + } + CODE + end + end + end + + # Confiugre a single pin for output and setup a method to refer to that pin, i.e.: + # + # output_pin 7, :as => :led + # + # would configure pin 7 as an output and let you refer to it from the then on by calling + # the `led` method in your loop like so: + # + # def loop + # digital_write led, ON + # end + # + def output_pin(num, opts={}) + raise ArgumentError, "can only define pin from Fixnum, got #{num.class}" unless num.is_a?(Fixnum) + @pin_modes[:output] << num + if opts[:as] + @declarations << "int _#{opts[ :as ]} = #{num};" + + accessor = [] + accessor << "int #{opts[ :as ]}() {" + accessor << "\treturn _#{opts[ :as ]};" + accessor << "}" + @accessors << accessor.join( "\n" ) + + @signatures << "int #{opts[ :as ]}();" + end + end + + # Like ArduinoSketch#output_pin but configure more than one output pin simultaneously. Takes an array of pin numbers. + def output_pins(nums) + ar = Array(nums) + ar.each {|n| output_pin(n)} + end + + # Confiugre a single pin for input and setup a method to refer to that pin, i.e.: + # + # input_pin 3, :as => :button + # + # would configure pin 3 as an input and let you refer to it from the then on by calling + # the `button` method in your loop like so: + # + # def loop + # digital_write led if digital_read button + # end + # + def input_pin(num, opts={}) + raise ArgumentError, "can only define pin from Fixnum, got #{num.class}" unless num.is_a?(Fixnum) + @pin_modes[:input] << num + if opts[:as] + @declarations << "int _#{opts[ :as ]} = #{num};" + + accessor = [] + accessor << "int #{opts[ :as ]}() {" + accessor << "\treturn _#{opts[ :as ]};" + accessor << "}" + @accessors << accessor.join( "\n" ) + + @signatures << "int #{opts[ :as ]}();" + end + @pullups << num if opts[:as] + end + + # Like ArduinoSketch#input_pin but configure more than one input pin simultaneously. Takes an array of pin numbers. + def input_pins(nums) + ar = Array(nums) + ar.each {|n| input_pin(n)} + end + + def add(st) #:nodoc: + @helper_methods << "\n#{st}\n" + end + + # Configure Arduino for serial communication. Optionally, set the baud rate: + # + # serial_begin :rate => 2400 + # + # default is 9600. See http://www.arduino.cc/en/Serial/Begin for more details. + # + def serial_begin(opts={}) + rate = opts[:rate] ? opts[:rate] : 9600 + @other_setup << "Serial.begin(#{rate});" + end + + # Treat a pair of digital I/O pins as a serial line. See: http://www.arduino.cc/en/Tutorial/SoftwareSerial + def software_serial(rx, tx, opts={}) + raise ArgumentError, "can only define rx from Fixnum, got #{rx.class}" unless rx.is_a?(Fixnum) + raise ArgumentError, "can only define tx from Fixnum, got #{tx.class}" unless tx.is_a?(Fixnum) + + output_pin(tx) + + rate = opts[:rate] ? opts[:rate] : 9600 + if opts[:as] + @declarations << "SoftwareSerial _#{opts[ :as ]} = SoftwareSerial(#{rx}, #{tx});" + accessor = [] + accessor << "SoftwareSerial& #{opts[ :as ]}() {" + accessor << "\treturn _#{opts[ :as ]};" + accessor << "}" + accessor << "int read(SoftwareSerial& s) {" + accessor << "\treturn s.read();" + accessor << "}" + accessor << "void println( SoftwareSerial& s, char* str ) {" + accessor << "\treturn s.println( str );" + accessor << "}" + accessor << "void print( SoftwareSerial& s, char* str ) {" + accessor << "\treturn s.print( str );" + accessor << "}" + accessor << "void println( SoftwareSerial& s, int i ) {" + accessor << "\treturn s.println( i );" + accessor << "}" + accessor << "void print( SoftwareSerial& s, int i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + @accessors << accessor.join( "\n" ) + + @signatures << "SoftwareSerial& #{opts[ :as ]}();" + + @other_setup << "_#{opts[ :as ]}.begin(#{rate});" + end + end + + def swser_LCDpa(tx, opts={}) + raise ArgumentError, "can only define tx from Fixnum, got #{tx.class}" unless tx.is_a?(Fixnum) + output_pin(tx) + + rate = opts[:rate] ? opts[:rate] : 9600 + if opts[:as] + @declarations << "SWSerLCDpa _#{opts[ :as ]} = SWSerLCDpa(#{tx});" + accessor = [] + accessor << "SWSerLCDpa& #{opts[ :as ]}() {" + accessor << "\treturn _#{opts[ :as ]};" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, uint8_t b ) {" + accessor << "\treturn s.print( b );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, const char *str ) {" + accessor << "\treturn s.print( str );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, char c ) {" + accessor << "\treturn s.print( c );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, int i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, unsigned int i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, long i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, unsigned long i ) {" + accessor << "\treturn s.print( i );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, long i, int b ) {" + accessor << "\treturn s.print( i, b );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, char* str ) {" + accessor << "\treturn s.println( str );" + accessor << "}" + accessor << "void print( SWSerLCDpa& s, char* str ) {" + accessor << "\treturn s.print( str );" + accessor << "}" + accessor << "void println(SWSerLCDpa& s) {" + accessor << "\treturn s.println();" + accessor << "}" + accessor << "void clearscr(SWSerLCDpa& s) {" + accessor << "\treturn s.clearscr();" + accessor << "}" + accessor << "void home(SWSerLCDpa& s) {" + accessor << "\treturn s.home();" + accessor << "}" + accessor << "void setgeo( SWSerLCDpa& s, int i ) {" + accessor << "\treturn s.setgeo( i );" + accessor << "}" + accessor << "void setintensity( SWSerLCDpa& s, int i ) {" + accessor << "\treturn s.setintensity( i );" + accessor << "}" + accessor << "void intoBignum(SWSerLCDpa& s) {" + accessor << "\treturn s.intoBignum();" + accessor << "}" + accessor << "void outofBignum(SWSerLCDpa& s) {" + accessor << "\treturn s.outofBignum();" + accessor << "}" + accessor << "void setxy( SWSerLCDpa& s, int x, int y) {" + accessor << "\treturn s.setxy( x, y );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, char c ) {" + accessor << "\treturn s.println( c );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, const char c[] ) {" + accessor << "\treturn s.println( c );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, uint8_t b ) {" + accessor << "\treturn s.println( b );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, int i ) {" + accessor << "\treturn s.println( i );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, long i ) {" + accessor << "\treturn s.println( i );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, unsigned long i ) {" + accessor << "\treturn s.println( i );" + accessor << "}" + accessor << "void println( SWSerLCDpa& s, long i, int b ) {" + accessor << "\treturn s.println( i, b );" + accessor << "}" + @accessors << accessor.join( "\n" ) + + @signatures << "SWSerLCDpa& #{opts[ :as ]}();" + + @other_setup << "_#{opts[ :as ]}.begin(#{rate});" + end + end + + def compose_setup #:nodoc: also composes headers and signatures + result = [] + + result << comment_box( "Auto-generated by RAD" ) + + result << "#include \n" + result << "#include \n" + result << "#include " + + + result << comment_box( 'method signatures' ) + result << "void loop();" + result << "void setup();" + @signatures.each {|sig| result << sig} + + result << "\n" + comment_box( "variable and accessors" ) + @declarations.each {|dec| result << dec} + result << "" # blank line + @accessors.each {|ac| result << ac} + + result << "\n" + comment_box( "assembler declarations" ) + unless @assembler_declarations.empty? + result << <<-CODE + extern "C" { + #{@assembler_declarations.join("\n")} + } + CODE + end + + result << "\n" + comment_box( "setup" ) + result << "void setup() {" + result << "\t// pin modes" + + @pin_modes.each do |k,v| + v.each do |value| + result << "\tpinMode(#{value}, #{k.to_s.upcase});" + end + end + + @pullups.each do |pin| + result << "\tdigitalWrite( #{pin}, HIGH ); // enable pull-up resistor for input" + end + + unless @other_setup.empty? + result << "\t// other setup" + result << @other_setup.join( "\n" ) + end + + result << "}\n" + + result << comment_box( "helper methods" ) + result << "\n// RAD built-in helpers" + result << @helper_methods.lstrip + + result << "\n// serial helpers" + result << serial_boilerplate.lstrip + + result << "\n" + comment_box( "main() function" ) + result << "int main() {" + result << "\tinit();" + result << "\tsetup();" + result << "\tfor( ;; ) { loop(); }" + result << "\treturn 0;" + result << "}" + + result << "\n" + comment_box( "loop! Autogenerated by RubyToC, sorry it's ugly." ) + + return result.join( "\n" ) + end + + + # Write inline assembler code. 'Name' is a symbol representing the name of the function to be defined in the assembly code; 'signature' is the function signature for the function being defined; and 'code' is the assembly code itself (both of these last two arguments are strings). See an example here: http://rad.rubyforge.org/examples/assembler_test.html + def assembler(name, signature, code) + @assembler_declarations << signature + assembler_code = <<-CODE + .file "#{name}.S" + .arch #{Makefile.hardware_params['mcu']} + .global __do_copy_data + .global __do_clear_bss + .text + .global #{name} + .type #{name}, @function + #{code} + CODE + + File.open(File.expand_path("#{RAD_ROOT}") + "/#{PROJECT_DIR_NAME}/#{name}.S", "w"){|f| f << assembler_code} + end + + def self.pre_process(sketch_string) #:nodoc: + result = sketch_string + result.gsub!("HIGH", "1") + result.gsub!("LOW", "0") + result.gsub!("ON", "1") + result.gsub!("OFF", "0") + result + end + + private + + def serial_boilerplate #:nodoc: + out = [] + out << "int serial_available() {" + out << "\treturn (Serial.available() > 0);" + out << "}" + + out << "char serial_read() {" + out << "\treturn (char) Serial.read();" + out << "}" + + out << "void serial_flush() {" + out << "\treturn Serial.flush();" + out << "}" + + out << "void serial_print( char str ) {" + out << "\treturn Serial.print( str );" + out << "}" + + out << "void serial_print( char* str ) {" + out << "\treturn Serial.print( str );" + out << "}" + + out << "void serial_print( int i ) {" + out << "\treturn Serial.print( i );" + out << "}" + + out << "void serial_print( long i ) {" + out << "\treturn Serial.print( i );" + out << "}" + + out << "void serial_println( char* str ) {" + out << "\treturn Serial.println( str );" + out << "}" + + out << "void serial_println( char str ) {" + out << "\treturn Serial.println( str );" + out << "}" + + out << "void serial_println( int i ) {" + out << "\treturn Serial.println( i );" + out << "}" + + out << "void serial_println( long i ) {" + out << "\treturn Serial.println( i );" + out << "}" + + return out.join( "\n" ) + end + + def comment_box( content ) #:nodoc: + out = [] + out << "/" * 74 + out << "// " + content + out << "/" * 74 + + return out.join( "\n" ) + end +end \ No newline at end of file diff --git a/pkg/rad-0.2.2/lib/rad/generators/makefile/makefile.erb b/pkg/rad-0.2.2/lib/rad/generators/makefile/makefile.erb new file mode 100644 index 0000000..b173cb6 --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad/generators/makefile/makefile.erb @@ -0,0 +1,243 @@ +# Arduino makefile +# +# This makefile allows you to build sketches from the command line +# without the Arduino environment (or Java). +# +# The Arduino environment does preliminary processing on a sketch before +# compiling it. If you're using this makefile instead, you'll need to do +# a few things differently: +# +# - Give your program's file a .cpp extension (e.g. foo.cpp). +# +# - Put this line at top of your code: #include +# +# - Write prototypes for all your functions (or define them before you +# call them). A prototype declares the types of parameters a +# function will take and what type of value it will return. This +# means that you can have a call to a function before the definition +# of the function. A function prototype looks like the first line of +# the function, with a semi-colon at the end. For example: +# int digitalRead(int pin); +# +# - Write a main() function for your program that returns an int, calls +# init() and setup() once (in that order), and then calls loop() +# repeatedly(): +# +# int main() +# { +# init(); +# setup(); +# +# for (;;) +# loop(); +# +# return 0; +# } +# +# Instructions for using the makefile: +# +# 1. Copy this file into the folder with your sketch. +# +# 2. Below, modify the line containing "TARGET" to refer to the name of +# of your program's file without an extension (e.g. TARGET = foo). +# +# 3. Modify the line containg "ARDUINO" to point the directory that +# contains the Arduino core (for normal Arduino installations, this +# is the hardware/cores/arduino sub-directory). +# +# 4. Modify the line containing "PORT" to refer to the filename +# representing the USB or serial connection to your Arduino board +# (e.g. PORT = /dev/tty.USB0). If the exact name of this file +# changes, you can use * as a wildcard (e.g. PORT = /dev/tty.USB*). +# +# 5. At the command line, change to the directory containing your +# program's file and the makefile. +# +# 6. Type "make" and press enter to compile/verify your program. +# +# 7. Type "make upload", reset your Arduino board, and press enter to +# upload your program to the Arduino board. +# +# $Id$ + +PORT = <%= params['serial_port'] %> +TARGET = <%= params['target'] %> +ARDUINO = <%= params['arduino_root'] %>/hardware/cores/arduino +SOFTWARE_SERIAL = <%= params['arduino_root'] %>/hardware/libraries/SoftwareSerial +LIBRARY_ROOT = <%= params['libraries_root'] %> +SRC = $(ARDUINO)/pins_arduino.c $(ARDUINO)/wiring.c \ +$(ARDUINO)/wiring_analog.c $(ARDUINO)/wiring_digital.c \ +$(ARDUINO)/wiring_pulse.c $(ARDUINO)/wiring_serial.c \ +$(ARDUINO)/wiring_shift.c $(ARDUINO)/WInterrupts.c +CXXSRC = $(ARDUINO)/HardwareSerial.cpp $(SOFTWARE_SERIAL)/SoftwareSerial.cpp<%= params['libraries'].collect{|l| " $(LIBRARY_ROOT)/#{ l }/#{l }.cpp"}.join('') %> +MCU = <%= params['mcu'] %> +<% if params['asm_files'] %>ASRC = <%= params['asm_files'].join(' ') %><% end %> +F_CPU = 16000000 +FORMAT = ihex +UPLOAD_RATE = 19200 +BIN_DIR = <%= params['arduino_root'] %>/hardware/tools/avr/bin + +# Name of this Makefile (used for "make depend"). +MAKEFILE = Makefile + +# Debugging format. +# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2. +# AVR (extended) COFF requires stabs, plus an avr-objcopy run. +DEBUG = stabs + +OPT = s + +# Place -D or -U options here +CDEFS = -DF_CPU=$(F_CPU) +CXXDEFS = -DF_CPU=$(F_CPU) + +# Place -I options here +CINCS = -I$(ARDUINO) -I$(SOFTWARE_SERIAL)<% params['libraries'].each do |l| %> -I$(LIBRARY_ROOT)/<%= l %><% end %> ++CXXINCS = -I$(ARDUINO) -I$(SOFTWARE_SERIAL)<% params['libraries'].each do |l| %> -I$(LIBRARY_ROOT)/<%= l %><% end %> + +# Compiler flag to set the C Standard level. +# c89 - "ANSI" C +# gnu89 - c89 plus GCC extensions +# c99 - ISO C99 standard (not yet fully implemented) +# gnu99 - c99 plus GCC extensions +CSTANDARD = -std=gnu99 +CDEBUG = -g$(DEBUG) +CWARN = -Wall -Wstrict-prototypes +CTUNING = -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums +#CEXTRA = -Wa,-adhlns=$(<:.c=.lst) + +CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CEXTRA) +CXXFLAGS = $(CDEFS) $(CINCS) -O$(OPT) +#ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs +LDFLAGS = -lm + + +# Programming support using avrdude. Settings and variables. +AVRDUDE_PROGRAMMER = stk500 +AVRDUDE_PORT = $(PORT) +AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex +AVRDUDE_FLAGS = -F -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) \ + -b $(UPLOAD_RATE) -C <%= params['arduino_root'] %>/hardware/tools/avr/etc/avrdude.conf + +# Program settings +CC = $(BIN_DIR)/avr-gcc +CXX = $(BIN_DIR)/avr-g++ +OBJCOPY = $(BIN_DIR)/avr-objcopy +OBJDUMP = $(BIN_DIR)/avr-objdump +AR = $(BIN_DIR)/avr-ar +SIZE = $(BIN_DIR)/avr-size +NM = $(BIN_DIR)/avr-nm +AVRDUDE = $(BIN_DIR)/avrdude +REMOVE = rm -f +MV = mv -f + +# Define all object files. +OBJ = $(SRC:.c=.o) $(CXXSRC:.cpp=.o) $(ASRC:.S=.o) + +# Define all listing files. +LST = $(ASRC:.S=.lst) $(CXXSRC:.cpp=.lst) $(SRC:.c=.lst) + +# Combine all necessary flags and optional flags. +# Add target processor to flags. +ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) +ALL_CXXFLAGS = -mmcu=$(MCU) -I. $(CXXFLAGS) +ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) + + +# Default target. +all: build + +build: elf hex + +elf: $(TARGET).elf +hex: $(TARGET).hex +eep: $(TARGET).eep +lss: $(TARGET).lss +sym: $(TARGET).sym + +# Program the device. +upload: $(TARGET).hex + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) + + + + +# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB. +COFFCONVERT=$(OBJCOPY) --debugging \ +--change-section-address .data-0x800000 \ +--change-section-address .bss-0x800000 \ +--change-section-address .noinit-0x800000 \ +--change-section-address .eeprom-0x810000 + + +coff: $(TARGET).elf + $(COFFCONVERT) -O coff-avr $(TARGET).elf $(TARGET).cof + + +extcoff: $(TARGET).elf + $(COFFCONVERT) -O coff-ext-avr $(TARGET).elf $(TARGET).cof + + +.SUFFIXES: .elf .hex .eep .lss .sym + +.elf.hex: + $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ + +.elf.eep: + -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ + --change-section-lma .eeprom=0 -O $(FORMAT) $< $@ + +# Create extended listing file from ELF output file. +.elf.lss: + $(OBJDUMP) -h -S $< > $@ + +# Create a symbol table from ELF output file. +.elf.sym: + $(NM) -n $< > $@ + + +core.a: $(OBJ) + @for i in $(OBJ); do echo $(AR) rcs core.a $$i; $(AR) rcs core.a $$i; done + +# Link: create ELF output file from library. +$(TARGET).elf: core.a + $(CC) $(ALL_CFLAGS) -o $@ $(TARGET).cpp -L. core.a $(LDFLAGS) + +# Compile: create object files from C++ source files. +.cpp.o: + $(CXX) -c $(ALL_CXXFLAGS) $< -o $@ + +# Compile: create object files from C source files. +.c.o: + $(CC) -c $(ALL_CFLAGS) $< -o $@ + + +# Compile: create assembler files from C source files. +.c.s: + $(CC) -S $(ALL_CFLAGS) $< -o $@ + + +# Assemble: create object files from assembler source files. +.S.o: + $(CC) -c $(ALL_ASFLAGS) $< -o $@ + + + +# Target: clean project. +clean: + $(REMOVE) $(TARGET).hex $(TARGET).eep $(TARGET).cof $(TARGET).elf \ + $(TARGET).map $(TARGET).sym $(TARGET).lss core.a \ + $(OBJ) $(LST) $(SRC:.c=.s) $(SRC:.c=.d) $(CXXSRC:.cpp=.s) $(CXXSRC:.cpp=.d) + +depend: + if grep '^# DO NOT DELETE' $(MAKEFILE) >/dev/null; \ + then \ + sed -e '/^# DO NOT DELETE/,$$d' $(MAKEFILE) > \ + $(MAKEFILE).$$$$ && \ + $(MV) $(MAKEFILE).$$$$ $(MAKEFILE); \ + fi + echo '# DO NOT DELETE THIS LINE -- make depend depends on it.' \ + >> $(MAKEFILE); \ + $(CC) -M -mmcu=$(MCU) $(CDEFS) $(CINCS) $(SRC) $(ASRC) >> $(MAKEFILE) + +.PHONY: all build elf hex eep lss sym program coff extcoff clean depend diff --git a/pkg/rad-0.2.2/lib/rad/generators/makefile/makefile.rb b/pkg/rad-0.2.2/lib/rad/generators/makefile/makefile.rb new file mode 100644 index 0000000..9c6a0cf --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad/generators/makefile/makefile.rb @@ -0,0 +1,39 @@ +require 'erb' +require 'yaml' + +class Makefile + class << self + + # build the sketch Makefile for the given template based on the values in its software and hardware config files + def compose_for_sketch(sketch_name) + params = hardware_params.merge software_params + params['target'] = sketch_name + + params['libraries_root'] = "#{File.expand_path(RAD_ROOT)}/vendor/libraries" + params['libraries'] = libraries + + params['asm_files'] = Dir.entries( File.expand_path(RAD_ROOT) + "/" + PROJECT_DIR_NAME ).select{|e| e =~ /\.S/} + + e = ERB.new File.read("#{File.dirname(__FILE__)}/makefile.erb") + + File.open("#{RAD_ROOT}/#{sketch_name}/Makefile", "w") do |f| + f << e.result(binding) + end + end + + def libraries + Dir.entries("#{RAD_ROOT}/vendor/libraries").select{|e| !(e =~ /\./)} + end + + def hardware_params + return @hardware_params if @hardware_params + return @hardware_params = YAML.load_file( "#{RAD_ROOT}/config/hardware.yml") + end + + def software_params + return @software_params if @software_params + return @software_params = YAML.load_file( "#{RAD_ROOT}/config/software.yml" ) + end + + end +end \ No newline at end of file diff --git a/pkg/rad-0.2.2/lib/rad/init.rb b/pkg/rad-0.2.2/lib/rad/init.rb new file mode 100644 index 0000000..44b9431 --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad/init.rb @@ -0,0 +1,12 @@ +RAD_ROOT = "#{File.dirname(__FILE__)}/../.." unless defined?(RAD_ROOT) + +unless defined?(PROJECT_DIR_NAME) + a = File.expand_path(File.expand_path("#{RAD_ROOT}")).split("/") + PROJECT_DIR_NAME = a[a.length-1] +end + + +%w(generators/makefile/makefile.rb arduino_sketch.rb tasks/rad.rb).each do |path| + require File.expand_path("#{RAD_ROOT}/vendor/rad/#{path}") +end + diff --git a/pkg/rad-0.2.2/lib/rad/tasks/build_and_make.rake b/pkg/rad-0.2.2/lib/rad/tasks/build_and_make.rake new file mode 100644 index 0000000..8c7dcdc --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad/tasks/build_and_make.rake @@ -0,0 +1,88 @@ +require File.expand_path(File.dirname(__FILE__) + "/../init.rb") +require 'ruby_to_ansi_c' + +namespace :make do + + desc "compile the sketch and then upload it to your Arduino board" + task :upload => :compile do + if Makefile.hardware_params['physical_reset'] + puts "Reset the Arduino and hit enter.\n==If your board doesn't need it, you can turn off this prompt in config/software.yml==" + STDIN.gets.chomp + end + sh %{cd #{RAD_ROOT}/#{@sketch_name}; make upload} + end + + desc "generate a makefile and use it to compile the .cpp" + task :compile => [:clean_sketch_dir, "build:sketch"] do # should also depend on "build:sketch" + Makefile.compose_for_sketch( @sketch_name ) + # not allowed? sh %{export PATH=#{Makefile.software_params[:arduino_root]}/tools/avr/bin:$PATH} + sh %{cd #{RAD_ROOT}/#{@sketch_name}; make depend; make} + end + + task :clean_sketch_dir => ["build:file_list", "build:sketch_dir"] do + @sketch_name = @file_names.first.split(".").first + + FileList.new(Dir.entries("#{RAD_ROOT}/#{@sketch_name}")).exclude("#{@sketch_name}.cpp").exclude(/^\./).each do |f| + sh %{rm #{RAD_ROOT}/#{@sketch_name}/#{f}} + end + end +end + +namespace :build do + + desc "actually build the sketch" + task :sketch => [:file_list, :sketch_dir, :setup] do + klass = @file_names.first.split(".").first.split("_").collect{|c| c.capitalize}.join("") + eval ArduinoSketch.pre_process(File.read(@file_names.first)) + @loop = RubyToAnsiC.translate(constantize(klass), "loop") + result = "#{@setup}\n#{@loop}\n" + name = @file_names.first.split(".").first + File.open("#{name}/#{name}.cpp", "w"){|f| f << result} + end + + # needs to write the library include and the method signatures + desc "build setup function" + task :setup do + klass = @file_names.first.split(".").first.split("_").collect{|c| c.capitalize}.join("") + eval "class #{klass} < ArduinoSketch; end;" + + @@as = ArduinoSketch.new + + delegate_methods = @@as.methods - Object.new.methods + delegate_methods.reject!{|m| m == "compose_setup"} + + delegate_methods.each do |meth| + constantize(klass).module_eval <<-CODE + def self.#{meth}(*args) + @@as.#{meth}(*args) + end + CODE + end + + eval File.read(@file_names.first) + @setup = @@as.compose_setup + end + + desc "setup target directory named after your sketch class" + task :sketch_dir => [:file_list] do + mkdir_p "#{@file_names.first.split(".").first}" + end + + task :file_list do + @file_names = [] + Dir.entries( File.expand_path(RAD_ROOT) ).each do |f| + if (f =~ /\.rb$/) + @file_names << f + end + end + end +end + +#yoinked from Rails +def constantize(camel_cased_word) + unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word + raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!" + end + + Object.module_eval("::#{$1}", __FILE__, __LINE__) +end \ No newline at end of file diff --git a/pkg/rad-0.2.2/lib/rad/tasks/rad.rb b/pkg/rad-0.2.2/lib/rad/tasks/rad.rb new file mode 100644 index 0000000..66f27fa --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad/tasks/rad.rb @@ -0,0 +1,2 @@ +require 'rake' +Dir["#{File.dirname(__FILE__)}/*.rake"].each { |ext| load ext } \ No newline at end of file diff --git a/pkg/rad-0.2.2/lib/rad/todo.txt b/pkg/rad-0.2.2/lib/rad/todo.txt new file mode 100644 index 0000000..d838118 --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad/todo.txt @@ -0,0 +1,13 @@ +TODO: +===== + +Future: + - complete library system: script/install library some_library + - bin/rad: + - setup and use a ~/.rad for new project defaults + - put repository on git + - project gallery (examples with movies) + - testing framework + - implement wire lib + - subclasses of ArduinoSketch (should just work, but what are they for?) + - simulator \ No newline at end of file diff --git a/pkg/rad-0.2.2/lib/rad/version.rb b/pkg/rad-0.2.2/lib/rad/version.rb new file mode 100644 index 0000000..fa10d05 --- /dev/null +++ b/pkg/rad-0.2.2/lib/rad/version.rb @@ -0,0 +1,9 @@ +module Rad #:nodoc: + module VERSION #:nodoc: + MAJOR = 0 + MINOR = 2 + TINY = 2 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/pkg/rad-0.2.2/scripts/txt2html b/pkg/rad-0.2.2/scripts/txt2html new file mode 100644 index 0000000..0048857 --- /dev/null +++ b/pkg/rad-0.2.2/scripts/txt2html @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'redcloth' +require 'syntax/convertors/html' +require 'erb' +require File.dirname(__FILE__) + '/../lib/rad/version.rb' + +version = Rad::VERSION::STRING +download = 'http://rubyforge.org/projects/rad' + +class Fixnum + def ordinal + # teens + return 'th' if (10..19).include?(self % 100) + # others + case self % 10 + when 1: return 'st' + when 2: return 'nd' + when 3: return 'rd' + else return 'th' + end + end +end + +class Time + def pretty + return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}" + end +end + +def convert_syntax(syntax, source) + return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^
|
$!,'') +end + +if ARGV.length >= 1 + src, template = ARGV + template ||= File.dirname(__FILE__) + '/../website/template.rhtml' + +else + puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html") + exit! +end + +template = ERB.new(File.open(template).read) + +title = nil +body = nil +File.open(src) do |fsrc| + title_text = fsrc.readline + body_text = fsrc.read + syntax_items = [] + body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)!m){ + ident = syntax_items.length + element, syntax, source = $1, $2, $3 + syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}" + "syntax-temp-#{ident}" + } + title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip + body = RedCloth.new(body_text).to_html + body.gsub!(%r!(?:
)?syntax-temp-(d+)(?:
)?!){ syntax_items[$1.to_i] } +end +stat = File.stat(src) +created = stat.ctime +modified = stat.mtime + +$stdout << template.result(binding) diff --git a/pkg/rad-0.2.2/setup.rb b/pkg/rad-0.2.2/setup.rb new file mode 100644 index 0000000..424a5f3 --- /dev/null +++ b/pkg/rad-0.2.2/setup.rb @@ -0,0 +1,1585 @@ +# +# setup.rb +# +# Copyright (c) 2000-2005 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public License version 2.1. +# + +unless Enumerable.method_defined?(:map) # Ruby 1.4.6 + module Enumerable + alias map collect + end +end + +unless File.respond_to?(:read) # Ruby 1.6 + def File.read(fname) + open(fname) {|f| + return f.read + } + end +end + +unless Errno.const_defined?(:ENOTEMPTY) # Windows? + module Errno + class ENOTEMPTY + # We do not raise this exception, implementation is not needed. + end + end +end + +def File.binread(fname) + open(fname, 'rb') {|f| + return f.read + } +end + +# for corrupted Windows' stat(2) +def File.dir?(path) + File.directory?((path[-1,1] == '/') ? path : path + '/') +end + + +class ConfigTable + + include Enumerable + + def initialize(rbconfig) + @rbconfig = rbconfig + @items = [] + @table = {} + # options + @install_prefix = nil + @config_opt = nil + @verbose = true + @no_harm = false + end + + attr_accessor :install_prefix + attr_accessor :config_opt + + attr_writer :verbose + + def verbose? + @verbose + end + + attr_writer :no_harm + + def no_harm? + @no_harm + end + + def [](key) + lookup(key).resolve(self) + end + + def []=(key, val) + lookup(key).set val + end + + def names + @items.map {|i| i.name } + end + + def each(&block) + @items.each(&block) + end + + def key?(name) + @table.key?(name) + end + + def lookup(name) + @table[name] or setup_rb_error "no such config item: #{name}" + end + + def add(item) + @items.push item + @table[item.name] = item + end + + def remove(name) + item = lookup(name) + @items.delete_if {|i| i.name == name } + @table.delete_if {|name, i| i.name == name } + item + end + + def load_script(path, inst = nil) + if File.file?(path) + MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path + end + end + + def savefile + '.config' + end + + def load_savefile + begin + File.foreach(savefile()) do |line| + k, v = *line.split(/=/, 2) + self[k] = v.strip + end + rescue Errno::ENOENT + setup_rb_error $!.message + "\n#{File.basename($0)} config first" + end + end + + def save + @items.each {|i| i.value } + File.open(savefile(), 'w') {|f| + @items.each do |i| + f.printf "%s=%s\n", i.name, i.value if i.value? and i.value + end + } + end + + def load_standard_entries + standard_entries(@rbconfig).each do |ent| + add ent + end + end + + def standard_entries(rbconfig) + c = rbconfig + + rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + if c['rubylibdir'] + # V > 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = c['rubylibdir'] + librubyverarch = c['archdir'] + siteruby = c['sitedir'] + siterubyver = c['sitelibdir'] + siterubyverarch = c['sitearchdir'] + elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = c['sitedir'] + siterubyver = "$siteruby/#{version}" + siterubyverarch = "$siterubyver/#{c['arch']}" + else + # V < 1.4.4 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" + siterubyver = siteruby + siterubyverarch = "$siterubyver/#{c['arch']}" + end + parameterize = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') + } + + if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] + else + makeprog = 'make' + end + + [ + ExecItem.new('installdirs', 'std/site/home', + 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ + {|val, table| + case val + when 'std' + table['rbdir'] = '$librubyver' + table['sodir'] = '$librubyverarch' + when 'site' + table['rbdir'] = '$siterubyver' + table['sodir'] = '$siterubyverarch' + when 'home' + setup_rb_error '$HOME was not set' unless ENV['HOME'] + table['prefix'] = ENV['HOME'] + table['rbdir'] = '$libdir/ruby' + table['sodir'] = '$libdir/ruby' + end + }, + PathItem.new('prefix', 'path', c['prefix'], + 'path prefix of target environment'), + PathItem.new('bindir', 'path', parameterize.call(c['bindir']), + 'the directory for commands'), + PathItem.new('libdir', 'path', parameterize.call(c['libdir']), + 'the directory for libraries'), + PathItem.new('datadir', 'path', parameterize.call(c['datadir']), + 'the directory for shared data'), + PathItem.new('mandir', 'path', parameterize.call(c['mandir']), + 'the directory for man pages'), + PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), + 'the directory for system configuration files'), + PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), + 'the directory for local state data'), + PathItem.new('libruby', 'path', libruby, + 'the directory for ruby libraries'), + PathItem.new('librubyver', 'path', librubyver, + 'the directory for standard ruby libraries'), + PathItem.new('librubyverarch', 'path', librubyverarch, + 'the directory for standard ruby extensions'), + PathItem.new('siteruby', 'path', siteruby, + 'the directory for version-independent aux ruby libraries'), + PathItem.new('siterubyver', 'path', siterubyver, + 'the directory for aux ruby libraries'), + PathItem.new('siterubyverarch', 'path', siterubyverarch, + 'the directory for aux ruby binaries'), + PathItem.new('rbdir', 'path', '$siterubyver', + 'the directory for ruby scripts'), + PathItem.new('sodir', 'path', '$siterubyverarch', + 'the directory for ruby extentions'), + PathItem.new('rubypath', 'path', rubypath, + 'the path to set to #! line'), + ProgramItem.new('rubyprog', 'name', rubypath, + 'the ruby program using for installation'), + ProgramItem.new('makeprog', 'name', makeprog, + 'the make program to compile ruby extentions'), + SelectItem.new('shebang', 'all/ruby/never', 'ruby', + 'shebang line (#!) editing mode'), + BoolItem.new('without-ext', 'yes/no', 'no', + 'does not compile/install ruby extentions') + ] + end + private :standard_entries + + def load_multipackage_entries + multipackage_entries().each do |ent| + add ent + end + end + + def multipackage_entries + [ + PackageSelectionItem.new('with', 'name,name...', '', 'ALL', + 'package names that you want to install'), + PackageSelectionItem.new('without', 'name,name...', '', 'NONE', + 'package names that you do not want to install') + ] + end + private :multipackage_entries + + ALIASES = { + 'std-ruby' => 'librubyver', + 'stdruby' => 'librubyver', + 'rubylibdir' => 'librubyver', + 'archdir' => 'librubyverarch', + 'site-ruby-common' => 'siteruby', # For backward compatibility + 'site-ruby' => 'siterubyver', # For backward compatibility + 'bin-dir' => 'bindir', + 'bin-dir' => 'bindir', + 'rb-dir' => 'rbdir', + 'so-dir' => 'sodir', + 'data-dir' => 'datadir', + 'ruby-path' => 'rubypath', + 'ruby-prog' => 'rubyprog', + 'ruby' => 'rubyprog', + 'make-prog' => 'makeprog', + 'make' => 'makeprog' + } + + def fixup + ALIASES.each do |ali, name| + @table[ali] = @table[name] + end + @items.freeze + @table.freeze + @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ + end + + def parse_opt(opt) + m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" + m.to_a[1,2] + end + + def dllext + @rbconfig['DLEXT'] + end + + def value_config?(name) + lookup(name).value? + end + + class Item + def initialize(name, template, default, desc) + @name = name.freeze + @template = template + @value = default + @default = default + @description = desc + end + + attr_reader :name + attr_reader :description + + attr_accessor :default + alias help_default default + + def help_opt + "--#{@name}=#{@template}" + end + + def value? + true + end + + def value + @value + end + + def resolve(table) + @value.gsub(%r<\$([^/]+)>) { table[$1] } + end + + def set(val) + @value = check(val) + end + + private + + def check(val) + setup_rb_error "config: --#{name} requires argument" unless val + val + end + end + + class BoolItem < Item + def config_type + 'bool' + end + + def help_opt + "--#{@name}" + end + + private + + def check(val) + return 'yes' unless val + case val + when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' + when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' + else + setup_rb_error "config: --#{@name} accepts only yes/no for argument" + end + end + end + + class PathItem < Item + def config_type + 'path' + end + + private + + def check(path) + setup_rb_error "config: --#{@name} requires argument" unless path + path[0,1] == '$' ? path : File.expand_path(path) + end + end + + class ProgramItem < Item + def config_type + 'program' + end + end + + class SelectItem < Item + def initialize(name, selection, default, desc) + super + @ok = selection.split('/') + end + + def config_type + 'select' + end + + private + + def check(val) + unless @ok.include?(val.strip) + setup_rb_error "config: use --#{@name}=#{@template} (#{val})" + end + val.strip + end + end + + class ExecItem < Item + def initialize(name, selection, desc, &block) + super name, selection, nil, desc + @ok = selection.split('/') + @action = block + end + + def config_type + 'exec' + end + + def value? + false + end + + def resolve(table) + setup_rb_error "$#{name()} wrongly used as option value" + end + + undef set + + def evaluate(val, table) + v = val.strip.downcase + unless @ok.include?(v) + setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" + end + @action.call v, table + end + end + + class PackageSelectionItem < Item + def initialize(name, template, default, help_default, desc) + super name, template, default, desc + @help_default = help_default + end + + attr_reader :help_default + + def config_type + 'package' + end + + private + + def check(val) + unless File.dir?("packages/#{val}") + setup_rb_error "config: no such package: #{val}" + end + val + end + end + + class MetaConfigEnvironment + def initialize(config, installer) + @config = config + @installer = installer + end + + def config_names + @config.names + end + + def config?(name) + @config.key?(name) + end + + def bool_config?(name) + @config.lookup(name).config_type == 'bool' + end + + def path_config?(name) + @config.lookup(name).config_type == 'path' + end + + def value_config?(name) + @config.lookup(name).config_type != 'exec' + end + + def add_config(item) + @config.add item + end + + def add_bool_config(name, default, desc) + @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + end + + def add_path_config(name, default, desc) + @config.add PathItem.new(name, 'path', default, desc) + end + + def set_config_default(name, default) + @config.lookup(name).default = default + end + + def remove_config(name) + @config.remove(name) + end + + # For only multipackage + def packages + raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer + @installer.packages + end + + # For only multipackage + def declare_packages(list) + raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer + @installer.packages = list + end + end + +end # class ConfigTable + + +# This module requires: #verbose?, #no_harm? +module FileOperations + + def mkdir_p(dirname, prefix = nil) + dirname = prefix + File.expand_path(dirname) if prefix + $stderr.puts "mkdir -p #{dirname}" if verbose? + return if no_harm? + + # Does not check '/', it's too abnormal. + dirs = File.expand_path(dirname).split(%r<(?=/)>) + if /\A[a-z]:\z/i =~ dirs[0] + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless File.dir?(path) + end + end + + def rm_f(path) + $stderr.puts "rm -f #{path}" if verbose? + return if no_harm? + force_remove_file path + end + + def rm_rf(path) + $stderr.puts "rm -rf #{path}" if verbose? + return if no_harm? + remove_tree path + end + + def remove_tree(path) + if File.symlink?(path) + remove_file path + elsif File.dir?(path) + remove_tree0 path + else + force_remove_file path + end + end + + def remove_tree0(path) + Dir.foreach(path) do |ent| + next if ent == '.' + next if ent == '..' + entpath = "#{path}/#{ent}" + if File.symlink?(entpath) + remove_file entpath + elsif File.dir?(entpath) + remove_tree0 entpath + else + force_remove_file entpath + end + end + begin + Dir.rmdir path + rescue Errno::ENOTEMPTY + # directory may not be empty + end + end + + def move_file(src, dest) + force_remove_file dest + begin + File.rename src, dest + rescue + File.open(dest, 'wb') {|f| + f.write File.binread(src) + } + File.chmod File.stat(src).mode, dest + File.unlink src + end + end + + def force_remove_file(path) + begin + remove_file path + rescue + end + end + + def remove_file(path) + File.chmod 0777, path + File.unlink path + end + + def install(from, dest, mode, prefix = nil) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix ? prefix + File.expand_path(dest) : dest + realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) + str = File.binread(from) + if diff?(str, realdest) + verbose_off { + rm_f realdest if File.exist?(realdest) + } + File.open(realdest, 'wb') {|f| + f.write str + } + File.chmod mode, realdest + + File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| + if prefix + f.puts realdest.sub(prefix, '') + else + f.puts realdest + end + } + end + end + + def diff?(new_content, path) + return true unless File.exist?(path) + new_content != File.binread(path) + end + + def command(*args) + $stderr.puts args.join(' ') if verbose? + system(*args) or raise RuntimeError, + "system(#{args.map{|a| a.inspect }.join(' ')}) failed" + end + + def ruby(*args) + command config('rubyprog'), *args + end + + def make(task = nil) + command(*[config('makeprog'), task].compact) + end + + def extdir?(dir) + File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") + end + + def files_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.file?("#{dir}/#{ent}") } + } + end + + DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) + + def directories_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT + } + end + +end + + +# This module requires: #srcdir_root, #objdir_root, #relpath +module HookScriptAPI + + def get_config(key) + @config[key] + end + + alias config get_config + + # obsolete: use metaconfig to change configuration + def set_config(key, val) + @config[key] = val + end + + # + # srcdir/objdir (works only in the package directory) + # + + def curr_srcdir + "#{srcdir_root()}/#{relpath()}" + end + + def curr_objdir + "#{objdir_root()}/#{relpath()}" + end + + def srcfile(path) + "#{curr_srcdir()}/#{path}" + end + + def srcexist?(path) + File.exist?(srcfile(path)) + end + + def srcdirectory?(path) + File.dir?(srcfile(path)) + end + + def srcfile?(path) + File.file?(srcfile(path)) + end + + def srcentries(path = '.') + Dir.open("#{curr_srcdir()}/#{path}") {|d| + return d.to_a - %w(. ..) + } + end + + def srcfiles(path = '.') + srcentries(path).select {|fname| + File.file?(File.join(curr_srcdir(), path, fname)) + } + end + + def srcdirectories(path = '.') + srcentries(path).select {|fname| + File.dir?(File.join(curr_srcdir(), path, fname)) + } + end + +end + + +class ToplevelInstaller + + Version = '3.4.1' + Copyright = 'Copyright (c) 2000-2005 Minero Aoki' + + TASKS = [ + [ 'all', 'do config, setup, then install' ], + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles ruby extentions and others' ], + [ 'install', 'installs files' ], + [ 'test', 'run all tests in test/' ], + [ 'clean', "does `make clean' for each extention" ], + [ 'distclean',"does `make distclean' for each extention" ] + ] + + def ToplevelInstaller.invoke + config = ConfigTable.new(load_rbconfig()) + config.load_standard_entries + config.load_multipackage_entries if multipackage? + config.fixup + klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) + klass.new(File.dirname($0), config).invoke + end + + def ToplevelInstaller.multipackage? + File.dir?(File.dirname($0) + '/packages') + end + + def ToplevelInstaller.load_rbconfig + if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + load File.expand_path(arg.split(/=/, 2)[1]) + $".push 'rbconfig.rb' + else + require 'rbconfig' + end + ::Config::CONFIG + end + + def initialize(ardir_root, config) + @ardir = File.expand_path(ardir_root) + @config = config + # cache + @valid_task_re = nil + end + + def config(key) + @config[key] + end + + def inspect + "#<#{self.class} #{__id__()}>" + end + + def invoke + run_metaconfigs + case task = parsearg_global() + when nil, 'all' + parsearg_config + init_installers + exec_config + exec_setup + exec_install + else + case task + when 'config', 'test' + ; + when 'clean', 'distclean' + @config.load_savefile if File.exist?(@config.savefile) + else + @config.load_savefile + end + __send__ "parsearg_#{task}" + init_installers + __send__ "exec_#{task}" + end + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig" + end + + def init_installers + @installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + # + # Hook Script API bases + # + + def srcdir_root + @ardir + end + + def objdir_root + '.' + end + + def relpath + '.' + end + + # + # Option Parsing + # + + def parsearg_global + while arg = ARGV.shift + case arg + when /\A\w+\z/ + setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) + return arg + when '-q', '--quiet' + @config.verbose = false + when '--verbose' + @config.verbose = true + when '--help' + print_usage $stdout + exit 0 + when '--version' + puts "#{File.basename($0)} version #{Version}" + exit 0 + when '--copyright' + puts Copyright + exit 0 + else + setup_rb_error "unknown global option '#{arg}'" + end + end + nil + end + + def valid_task?(t) + valid_task_re() =~ t + end + + def valid_task_re + @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ + end + + def parsearg_no_options + unless ARGV.empty? + task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) + setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" + end + end + + alias parsearg_show parsearg_no_options + alias parsearg_setup parsearg_no_options + alias parsearg_test parsearg_no_options + alias parsearg_clean parsearg_no_options + alias parsearg_distclean parsearg_no_options + + def parsearg_config + evalopt = [] + set = [] + @config.config_opt = [] + while i = ARGV.shift + if /\A--?\z/ =~ i + @config.config_opt = ARGV.dup + break + end + name, value = *@config.parse_opt(i) + if @config.value_config?(name) + @config[name] = value + else + evalopt.push [name, value] + end + set.push name + end + evalopt.each do |name, value| + @config.lookup(name).evaluate value, @config + end + # Check if configuration is valid + set.each do |n| + @config[n] if @config.value_config?(n) + end + end + + def parsearg_install + @config.no_harm = false + @config.install_prefix = '' + while a = ARGV.shift + case a + when '--no-harm' + @config.no_harm = true + when /\A--prefix=/ + path = a.split(/=/, 2)[1] + path = File.expand_path(path) unless path[0,1] == '/' + @config.install_prefix = path + else + setup_rb_error "install: unknown option #{a}" + end + end + end + + def print_usage(out) + out.puts 'Typical Installation Procedure:' + out.puts " $ ruby #{File.basename $0} config" + out.puts " $ ruby #{File.basename $0} setup" + out.puts " # ruby #{File.basename $0} install (may require root privilege)" + out.puts + out.puts 'Detailed Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-24s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, ' --help', 'print this message' + out.printf fmt, ' --version', 'print version and quit' + out.printf fmt, ' --copyright', 'print copyright and quit' + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf fmt, name, desc + end + + fmt = " %-24s %s [%s]\n" + out.puts + out.puts 'Options for CONFIG or ALL:' + @config.each do |item| + out.printf fmt, item.help_opt, item.description, item.help_default + end + out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" + out.puts + out.puts 'Options for INSTALL:' + out.printf fmt, '--no-harm', 'only display what to do if given', 'off' + out.printf fmt, '--prefix=path', 'install path prefix', '' + out.puts + end + + # + # Task Handlers + # + + def exec_config + @installer.exec_config + @config.save # must be final + end + + def exec_setup + @installer.exec_setup + end + + def exec_install + @installer.exec_install + end + + def exec_test + @installer.exec_test + end + + def exec_show + @config.each do |i| + printf "%-20s %s\n", i.name, i.value if i.value? + end + end + + def exec_clean + @installer.exec_clean + end + + def exec_distclean + @installer.exec_distclean + end + +end # class ToplevelInstaller + + +class ToplevelInstallerMulti < ToplevelInstaller + + include FileOperations + + def initialize(ardir_root, config) + super + @packages = directories_of("#{@ardir}/packages") + raise 'no package exists' if @packages.empty? + @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig", self + @packages.each do |name| + @config.load_script "#{@ardir}/packages/#{name}/metaconfig" + end + end + + attr_reader :packages + + def packages=(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + + def init_installers + @installers = {} + @packages.each do |pack| + @installers[pack] = Installer.new(@config, + "#{@ardir}/packages/#{pack}", + "packages/#{pack}") + end + with = extract_selection(config('with')) + without = extract_selection(config('without')) + @selected = @installers.keys.select {|name| + (with.empty? or with.include?(name)) \ + and not without.include?(name) + } + end + + def extract_selection(list) + a = list.split(/,/) + a.each do |name| + setup_rb_error "no such package: #{name}" unless @installers.key?(name) + end + a + end + + def print_usage(f) + super + f.puts 'Inluded packages:' + f.puts ' ' + @packages.sort.join(' ') + f.puts + end + + # + # Task Handlers + # + + def exec_config + run_hook 'pre-config' + each_selected_installers {|inst| inst.exec_config } + run_hook 'post-config' + @config.save # must be final + end + + def exec_setup + run_hook 'pre-setup' + each_selected_installers {|inst| inst.exec_setup } + run_hook 'post-setup' + end + + def exec_install + run_hook 'pre-install' + each_selected_installers {|inst| inst.exec_install } + run_hook 'post-install' + end + + def exec_test + run_hook 'pre-test' + each_selected_installers {|inst| inst.exec_test } + run_hook 'post-test' + end + + def exec_clean + rm_f @config.savefile + run_hook 'pre-clean' + each_selected_installers {|inst| inst.exec_clean } + run_hook 'post-clean' + end + + def exec_distclean + rm_f @config.savefile + run_hook 'pre-distclean' + each_selected_installers {|inst| inst.exec_distclean } + run_hook 'post-distclean' + end + + # + # lib + # + + def each_selected_installers + Dir.mkdir 'packages' unless File.dir?('packages') + @selected.each do |pack| + $stderr.puts "Processing the package `#{pack}' ..." if verbose? + Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") + Dir.chdir "packages/#{pack}" + yield @installers[pack] + Dir.chdir '../..' + end + end + + def run_hook(id) + @root_installer.run_hook id + end + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + +end # class ToplevelInstallerMulti + + +class Installer + + FILETYPES = %w( bin lib ext data conf man ) + + include FileOperations + include HookScriptAPI + + def initialize(config, srcroot, objroot) + @config = config + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{self.class} #{File.basename(@srcdir)}>" + end + + def noop(rel) + end + + # + # Hook Script API base methods + # + + def srcdir_root + @srcdir + end + + def objdir_root + @objdir + end + + def relpath + @currdir + end + + # + # Config Access + # + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + + def verbose_off + begin + save, @config.verbose = @config.verbose?, false + yield + ensure + @config.verbose = save + end + end + + # + # TASK config + # + + def exec_config + exec_task_traverse 'config' + end + + alias config_dir_bin noop + alias config_dir_lib noop + + def config_dir_ext(rel) + extconf if extdir?(curr_srcdir()) + end + + alias config_dir_data noop + alias config_dir_conf noop + alias config_dir_man noop + + def extconf + ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt + end + + # + # TASK setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin(rel) + files_of(curr_srcdir()).each do |fname| + update_shebang_line "#{curr_srcdir()}/#{fname}" + end + end + + alias setup_dir_lib noop + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + alias setup_dir_data noop + alias setup_dir_conf noop + alias setup_dir_man noop + + def update_shebang_line(path) + return if no_harm? + return if config('shebang') == 'never' + old = Shebang.load(path) + if old + $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 + new = new_shebang(old) + return if new.to_s == old.to_s + else + return unless config('shebang') == 'all' + new = Shebang.new(config('rubypath')) + end + $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? + open_atomic_writer(path) {|output| + File.open(path, 'rb') {|f| + f.gets if old # discard + output.puts new.to_s + output.print f.read + } + } + end + + def new_shebang(old) + if /\Aruby/ =~ File.basename(old.cmd) + Shebang.new(config('rubypath'), old.args) + elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' + Shebang.new(config('rubypath'), old.args[1..-1]) + else + return old unless config('shebang') == 'all' + Shebang.new(config('rubypath')) + end + end + + def open_atomic_writer(path, &block) + tmpfile = File.basename(path) + '.tmp' + begin + File.open(tmpfile, 'wb', &block) + File.rename tmpfile, File.basename(path) + ensure + File.unlink tmpfile if File.exist?(tmpfile) + end + end + + class Shebang + def Shebang.load(path) + line = nil + File.open(path) {|f| + line = f.gets + } + return nil unless /\A#!/ =~ line + parse(line) + end + + def Shebang.parse(line) + cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') + new(cmd, args) + end + + def initialize(cmd, args = []) + @cmd = cmd + @args = args + end + + attr_reader :cmd + attr_reader :args + + def to_s + "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") + end + end + + # + # TASK install + # + + def exec_install + rm_f 'InstalledFiles' + exec_task_traverse 'install' + end + + def install_dir_bin(rel) + install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 + end + + def install_dir_lib(rel) + install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 + end + + def install_dir_ext(rel) + return unless extdir?(curr_srcdir()) + install_files rubyextentions('.'), + "#{config('sodir')}/#{File.dirname(rel)}", + 0555 + end + + def install_dir_data(rel) + install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 + end + + def install_dir_conf(rel) + # FIXME: should not remove current config files + # (rename previous file to .old/.org) + install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 + end + + def install_dir_man(rel) + install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 + end + + def install_files(list, dest, mode) + mkdir_p dest, @config.install_prefix + list.each do |fname| + install fname, dest, mode, @config.install_prefix + end + end + + def libfiles + glob_reject(%w(*.y *.output), targetfiles()) + end + + def rubyextentions(dir) + ents = glob_select("*.#{@config.dllext}", targetfiles()) + if ents.empty? + setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" + end + ents + end + + def targetfiles + mapdir(existfiles() - hookfiles()) + end + + def mapdir(ents) + ents.map {|ent| + if File.exist?(ent) + then ent # objdir + else "#{curr_srcdir()}/#{ent}" # srcdir + end + } + end + + # picked up many entries from cvs-1.11.1/src/ignore.c + JUNK_FILES = %w( + core RCSLOG tags TAGS .make.state + .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb + *~ *.old *.bak *.BAK *.orig *.rej _$* *$ + + *.org *.in .* + ) + + def existfiles + glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) + end + + def hookfiles + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| + %w( config setup install clean ).map {|t| sprintf(fmt, t) } + }.flatten + end + + def glob_select(pat, ents) + re = globs2re([pat]) + ents.select {|ent| re =~ ent } + end + + def glob_reject(pats, ents) + re = globs2re(pats) + ents.reject {|ent| re =~ ent } + end + + GLOB2REGEX = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + + def globs2re(pats) + /\A(?:#{ + pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') + })\z/ + end + + # + # TASK test + # + + TESTDIR = 'test' + + def exec_test + unless File.directory?('test') + $stderr.puts 'no test in this package' if verbose? + return + end + $stderr.puts 'Running tests...' if verbose? + begin + require 'test/unit' + rescue LoadError + setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' + end + runner = Test::Unit::AutoRunner.new(true) + runner.to_run << TESTDIR + runner.run + end + + # + # TASK clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias clean_dir_bin noop + alias clean_dir_lib noop + alias clean_dir_data noop + alias clean_dir_conf noop + alias clean_dir_man noop + + def clean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'clean' if File.file?('Makefile') + end + + # + # TASK distclean + # + + def exec_distclean + exec_task_traverse 'distclean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias distclean_dir_bin noop + alias distclean_dir_lib noop + + def distclean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'distclean' if File.file?('Makefile') + end + + alias distclean_dir_data noop + alias distclean_dir_conf noop + alias distclean_dir_man noop + + # + # Traversing + # + + def exec_task_traverse(task) + run_hook "pre-#{task}" + FILETYPES.each do |type| + if type == 'ext' and config('without-ext') == 'yes' + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, "#{task}_dir_#{type}" + end + run_hook "post-#{task}" + end + + def traverse(task, rel, mid) + dive_into(rel) { + run_hook "pre-#{task}" + __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') + directories_of(curr_srcdir()).each do |d| + traverse task, "#{rel}/#{d}", mid + end + run_hook "post-#{task}" + } + end + + def dive_into(rel) + return unless File.dir?("#{@srcdir}/#{rel}") + + dir = File.basename(rel) + Dir.mkdir dir unless File.dir?(dir) + prevdir = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir prevdir + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + + def run_hook(id) + path = [ "#{curr_srcdir()}/#{id}", + "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } + return unless path + begin + instance_eval File.read(path), path, 1 + rescue + raise if $DEBUG + setup_rb_error "hook #{path} failed:\n" + $!.message + end + end + +end # class Installer + + +class SetupError < StandardError; end + +def setup_rb_error(msg) + raise SetupError, msg +end + +if $0 == __FILE__ + begin + ToplevelInstaller.invoke + rescue SetupError + raise if $DEBUG + $stderr.puts $!.message + $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." + exit 1 + end +end diff --git a/pkg/rad-0.2.2/spec/models/arduino_sketch_spec.rb b/pkg/rad-0.2.2/spec/models/arduino_sketch_spec.rb new file mode 100644 index 0000000..e33b4a6 --- /dev/null +++ b/pkg/rad-0.2.2/spec/models/arduino_sketch_spec.rb @@ -0,0 +1,82 @@ +require File.dirname(__FILE__) + '/spec_helper.rb' +require File.expand_path(File.dirname(__FILE__) + "/../../lib/rad/arduino_sketch.rb") + +context "Arduino#serial_begin" do + setup do + @as = ArduinoSketch.new + end + + specify "should default baud_rate to 9600" do + @as.serial_begin + @as.instance_variable_get("@other_setup").should include("Serial.begin(9600);") + end + specify "should set an alternate baud_rate if told" do + @as.serial_begin :rate => 2400 + @as.instance_variable_get("@other_setup").should include("Serial.begin(2400);") + end + specify "should add the correct function call to the composed_setup" do + @as.serial_begin + @as.compose_setup.should match(Regexp.new(Regexp.escape("Serial.begin(9600);"))) + end +end + + +context "Arduino Base" do + setup do + @as = ArduinoSketch.new + end + + specify "output_pin method without :as arg. should add the pin to the pin_mode hash's output list and leave the declarations alone" do + @as.output_pin 1 + @as.instance_variable_get("@declarations").first.should == nil + @as.instance_variable_get("@pin_modes")[:output].should include(1) + end + + specify "output_pin method with :as arg. should add the pin to the pin_mode hash's output list write the appropriate declaration and accessor" do + @as.output_pin 3, :as => :led + @as.instance_variable_get("@declarations").first.should == "int _led = 3;" + @as.instance_variable_get("@accessors").first.should == "int led(){\nreturn _led;\n}" + @as.instance_variable_get("@pin_modes")[:output].should include(3) + end + + specify "output_pins method should add the pin to the pin_mode hash's output list and leave the declarations and accessors alone" do + @as.output_pins [5,4,3,2] + @as.instance_variable_get("@pin_modes")[:output].should include(5) + @as.instance_variable_get("@pin_modes")[:output].should include(4) + @as.instance_variable_get("@pin_modes")[:output].should include(3) + @as.instance_variable_get("@pin_modes")[:output].should include(2) + @as.instance_variable_get("@declarations").first.should == nil + @as.instance_variable_get("@accessors").first.should == nil + end + + specify "input_pin method with :as arg. should add the pin to the pin_mode hash's input list write the appropriate declaration and accessor" do + @as.input_pin 1, :as => :knob + @as.instance_variable_get("@declarations").first.should == "int _knob = 1;" + @as.instance_variable_get("@accessors").first.should == "int knob(){\nreturn _knob;\n}" + @as.instance_variable_get("@pin_modes")[:input].should include(1) + end + + specify "input_pins method should add the pins to the pin_mode hash's input list and leave the declarations and accessors alone" do + @as.input_pins [5,4,3,2] + @as.instance_variable_get("@pin_modes")[:input].should include(5) + @as.instance_variable_get("@pin_modes")[:input].should include(4) + @as.instance_variable_get("@pin_modes")[:input].should include(3) + @as.instance_variable_get("@pin_modes")[:input].should include(2) + @as.instance_variable_get("@declarations").first.should == nil + @as.instance_variable_get("@accessors").first.should == nil + end + + specify "compose_setup should append each appropriate pinMode statement and :as accessor to the setup_function string with a newline" do + @as.output_pins [1, 2] + @as.input_pin 3, :as => :button + + result = @as.send :compose_setup + + result.should match(Regexp.new(Regexp.escape("pinMode(1, OUTPUT);\n"))) + result.should match(Regexp.new(Regexp.escape("pinMode(2, OUTPUT);\n"))) + result.should match(Regexp.new(Regexp.escape("pinMode(3, INPUT);\n"))) + result.should match(Regexp.new(Regexp.escape("int _button = 3;\n"))) + result.should match(Regexp.new(Regexp.escape("int button(){\nreturn _button;\n}"))) + end + +end \ No newline at end of file diff --git a/pkg/rad-0.2.2/spec/models/spec_helper.rb b/pkg/rad-0.2.2/spec/models/spec_helper.rb new file mode 100644 index 0000000..2660e27 --- /dev/null +++ b/pkg/rad-0.2.2/spec/models/spec_helper.rb @@ -0,0 +1,2 @@ +require 'rubygems' +require 'spec' diff --git a/pkg/rad-0.2.2/spec/spec.opts b/pkg/rad-0.2.2/spec/spec.opts new file mode 100644 index 0000000..cf6add7 --- /dev/null +++ b/pkg/rad-0.2.2/spec/spec.opts @@ -0,0 +1 @@ +--colour \ No newline at end of file diff --git a/pkg/rad-0.2.2/website/examples/assembler_test.rb.html b/pkg/rad-0.2.2/website/examples/assembler_test.rb.html new file mode 100644 index 0000000..92232d0 --- /dev/null +++ b/pkg/rad-0.2.2/website/examples/assembler_test.rb.html @@ -0,0 +1,73 @@ + + + assembler_test.rb.html + + .ruby .normal {} + .ruby .comment { color: #888; font-style: italic; } + .ruby .keyword { color: #A00; font-weight: bold; } + .ruby .method { color: #077; } + .ruby .class { color: #074; } + .ruby .module { color: #050; } + .ruby .punct { color: #447; font-weight: bold; } + .ruby .symbol { color: #099; } + .ruby .string { color: #944; } + .ruby .char { color: #F07; } + .ruby .ident { color: #004; } + .ruby .constant { color: #07F; } + .ruby .regex { color: #B66; } + .ruby .number { color: #D55; } + .ruby .attribute { color: #377; } + .ruby .global { color: #3B7; } + .ruby .expr { color: #227; } + + + +
+
# Hardware: Connect to serial output with screen:
+#           $ screen /dev/tty/path.to.your.usb 9600
+
+class AssemblerTest < ArduinoSketch
+  vars :a => 10, :b => 4
+  serial_begin
+  
+  def loop
+    serial_println product(a,b)
+  end
+  
+  assembler( :product, "int product(int a, int b);",
+    <<-CODE
+    product:
+      	mov  r18,r24	; move a to another register
+      	ldi  r24,0		; clear running sum, used to coalesce product
+      	ldi  r25,0		; sum = 0
+      
+      .loop:
+      	tst  r18		  ; is a = 0? if so, we're done
+      	breq .end
+      
+      	mov  r19,r18	; copy a
+      	andi r19,1		; is a % 2 == 0
+      	breq .skip		
+      
+      	add  r24,r22	; add b to sum
+      	adc  r25,r23
+      
+      .skip:
+      	lsr  r18		  ; divide a by 2
+      
+      	clc			
+      	rol  r22		  ; multiply b by 2
+      	rol  r23
+      	rjmp .loop
+      
+      .end:
+      	ret
+      	.size product, .-product
+    CODE
+  )
+end
+
+
+ + diff --git a/pkg/rad-0.2.2/website/examples/gps_reader.rb.html b/pkg/rad-0.2.2/website/examples/gps_reader.rb.html new file mode 100644 index 0000000..9a30b88 --- /dev/null +++ b/pkg/rad-0.2.2/website/examples/gps_reader.rb.html @@ -0,0 +1,39 @@ + + + gps_reader.rb.html + + .ruby .normal {} + .ruby .comment { color: #888; font-style: italic; } + .ruby .keyword { color: #A00; font-weight: bold; } + .ruby .method { color: #077; } + .ruby .class { color: #074; } + .ruby .module { color: #050; } + .ruby .punct { color: #447; font-weight: bold; } + .ruby .symbol { color: #099; } + .ruby .string { color: #944; } + .ruby .char { color: #F07; } + .ruby .ident { color: #004; } + .ruby .constant { color: #07F; } + .ruby .regex { color: #B66; } + .ruby .number { color: #D55; } + .ruby .attribute { color: #377; } + .ruby .global { color: #3B7; } + .ruby .expr { color: #227; } + + + +
+
class GpsReader < ArduinoSketch
+  output_pin 13, :as => :led
+  software_serial 6, 7, :as => :gps
+  serial_begin
+
+  def loop
+    digitalWrite(led, true)
+    serial_print(gps.read)
+  end
+end
+
+ + diff --git a/pkg/rad-0.2.2/website/examples/hello_world.rb.html b/pkg/rad-0.2.2/website/examples/hello_world.rb.html new file mode 100644 index 0000000..80b47a2 --- /dev/null +++ b/pkg/rad-0.2.2/website/examples/hello_world.rb.html @@ -0,0 +1,38 @@ + + + hello_world.rb.html + + .ruby .normal {} + .ruby .comment { color: #888; font-style: italic; } + .ruby .keyword { color: #A00; font-weight: bold; } + .ruby .method { color: #077; } + .ruby .class { color: #074; } + .ruby .module { color: #050; } + .ruby .punct { color: #447; font-weight: bold; } + .ruby .symbol { color: #099; } + .ruby .string { color: #944; } + .ruby .char { color: #F07; } + .ruby .ident { color: #004; } + .ruby .constant { color: #07F; } + .ruby .regex { color: #B66; } + .ruby .number { color: #D55; } + .ruby .attribute { color: #377; } + .ruby .global { color: #3B7; } + .ruby .expr { color: #227; } + + + +
+
# Hardware: LED connected on pin 7
+
+class HelloWorld < ArduinoSketch
+  output_pin 7, :as => :led
+  def loop
+    blink led, 500
+  end
+end
+
+
+ + diff --git a/pkg/rad-0.2.2/website/examples/serial_motor.rb.html b/pkg/rad-0.2.2/website/examples/serial_motor.rb.html new file mode 100644 index 0000000..8dfd891 --- /dev/null +++ b/pkg/rad-0.2.2/website/examples/serial_motor.rb.html @@ -0,0 +1,41 @@ + + + serial_motor.rb.html + + .ruby .normal {} + .ruby .comment { color: #888; font-style: italic; } + .ruby .keyword { color: #A00; font-weight: bold; } + .ruby .method { color: #077; } + .ruby .class { color: #074; } + .ruby .module { color: #050; } + .ruby .punct { color: #447; font-weight: bold; } + .ruby .symbol { color: #099; } + .ruby .string { color: #944; } + .ruby .char { color: #F07; } + .ruby .ident { color: #004; } + .ruby .constant { color: #07F; } + .ruby .regex { color: #B66; } + .ruby .number { color: #D55; } + .ruby .attribute { color: #377; } + .ruby .global { color: #3B7; } + .ruby .expr { color: #227; } + + + +
+
# Hardware: motor control circuit (i.e. TIP-120 control pin)
+#           connected at pin 7.
+#     Demo: http://www.youtube.com/watch?v=7OguEBfdTe0
+
+class SerialMotor < ArduinoSketch
+  output_pin 7, :as => :motor
+  serial_begin
+  
+  def loop
+    digitalWrite(motor, serial_read) if serial_available
+  end
+end
+
+ + diff --git a/pkg/rad-0.2.2/website/index.html b/pkg/rad-0.2.2/website/index.html new file mode 100644 index 0000000..fed33ce --- /dev/null +++ b/pkg/rad-0.2.2/website/index.html @@ -0,0 +1,177 @@ + + + + + + + RAD + + + + + + +
+
+
+

Get Version

+ 0.2.2 +
+ +
+

RAD

+

→ ‘Ruby Arduino Development’

+ + + + +

What?

+ + +

RAD is a framework for programming the Arduino physcial computing platform using Ruby. RAD converts Ruby scripts written using a set of Rails-like conventions and helpers into C source code which can be compiled and run on the Arduino microcontroller. It also provides a set of Rake tasks for automating the compilation and upload process.

+ +
+ +

Demo: 'Hello World'

+ +

Here's a basic demo of RAD in action. In this movie, we'll write, compile, and upload the universal physical computing 'Hello World': a single flashing LED. +

+ Note: This movie was made using an old version of the Arduino board which required a hardware reset before being able to accept a new sketch. More recent versions of the board don't have this requirement and hence as of version 0.2.0, RAD no longer prompts for reset when running 'rake make:upload' (thought the option is still available for older boards.) +

+
+

Why?

+ + +

While duplicating the functionality of the well-designed Arduino software interface in Ruby may seem like an odd or redundant goal, RAD has further ambitions! Bootstrapping the ability to write microcontroller code in a high level dynamic language like Ruby could greatly ease the creation of all the luxurious development aids the users of such a language have come to expect: developer testing, platform independence, easy metaprogramming, etc.

+ + +

Installing

+ + +

$ sudo gem install rad

+ + +

You’ll also need to have the Arduino environment installed, which you can get from the Arduino website. RAD currently requires Arduino 0010, but we try to keep it up-to-date with new Arduino releases.

+ + +

The Basics

+ + +

$ rad my_sketch

+ + +

This command will create a new RAD project directory (my_sketch/) inside of the current directory that contains a blank script in which you can write your own RAD code, as well as a full install of the RAD support infrastructure (in vendor/). A sample ‘hello world’ script in RAD will look like this:

+ + +
+class MySketch < ArduinoSketch
+  output_pin 7, :as => :led
+  def loop
+    blink led, 500
+  end
+end
+
+ +

Once your code is written, your relevant local configuration properly setup in config/hardware.rb and config/software.rb, and an Arduino with the corresponding circuit (a 220ohm resistor and an LED wired in series between Arduino pin 7 and ground) is connected to your computer via serial, run:

+ + +

$ rake make:upload

+ + +

This will:

+ + +
    +
  • generate the correct Arduino C++ code from your sketch
  • +
  • dynamically prepare a localized version of the default Arduino Makefile
  • +
  • compile your sketch
  • +
  • prompt you to hit the reset button on your Arduino (if necessary!)
  • +
  • upload your compiled binary onto your Arduino
  • +
+ + +

Documentation and The Arduino API

+ + +

Most of the Arduino software API should be working correctly at this point. Documentation for RAD's version of things and details about usage of the wider Arduino API are available in the RAD rDocs.

+ +

Demo: Serial Communication

+

+ To demonstrate some of the more advanced features of RAD, here's a movie showing how to program the Arduino to listen to serial communication from a computer. +

+
+ + Note: The same comment from above applies here about the hardware reset. Also, extra points are available if you recognize the logo on the flag in the video. +

+
+ +

For more examples of RAD in action, see the RAD example directory.

+

RAD Needs You!

+ + +

All the many discipline-crossing skills required for a project like RAD make for lots of opportunities to help out: Have you written lots of sketches exploring the obscure depths of the Arduino library? Do you run the Arduino development tool chain on an obscure (i.e., non-OS X) platform? Do you develop for other AVR or PIC microcontrollers? Are you a C/C++ ninja? Or even C/C++ competent?

+ + +

There’s lots to do.

+ + +

If you’re looking for a place to dive in and don’t know quite where, email the RAD Google Group; we're friendly! If you want to start by taking a log at the code, the trunk repository is svn://rubyforge.org/var/svn/rad/trunk for anonymous access.

+ + +

License

+ + +

This code is free to use under the terms of the GPL 2.0 license, just like the Arduino software library itself.

+ + +

Contact

+ + +

Comments, questions, heckles, attacks, praises, and, (most especially) patches and contributions are welcome! Send email to the RAD mailing list.

+ +

Who

+

Greg Borenstein is RAD's original author and main maintainer with significant contributions from Ben Bleything and Brian Riley, patches from Scott Windsor and David Michael, and the support of the the Ruby Arduino Development Google Group.

+ +

+ Dr Nic, 18th November 2007
+ Theme extended from Paul Battley +

+
+ + + + + + diff --git a/pkg/rad-0.2.2/website/index.txt b/pkg/rad-0.2.2/website/index.txt new file mode 100644 index 0000000..199bb82 --- /dev/null +++ b/pkg/rad-0.2.2/website/index.txt @@ -0,0 +1,64 @@ +h1. RAD + +h1. → 'Ruby Arduino Development' + +h2. What? + +RAD is a framework for programming the Arduino physcial computing platform using Ruby. RAD converts Ruby scripts written using a set of Rails-like conventions and helpers into C source code which can be compiled and run on the Arduino microcontroller. It also provides a set of Rake tasks for automating the compilation and upload process. + +h2. Why? + +While duplicating the functionality of the well-designed Arduino software interface in Ruby may seem like an odd or redundant goal, RAD has further ambitions! Bootstrapping the ability to write microcontroller code in a high level dynamic language like Ruby could greatly ease the creation of all the luxurious development aids the users of such a language have come to expect: developer testing, platform independence, easy metaprogramming, etc. + +h2. Installing + +@$ sudo gem install rad@ + +You'll also need to have the Arduino environment installed, which you can get from "the Arduino website":http://www.arduino.cc/en/Main/Software. RAD currently requires Arduino 0010, but we try to keep it up-to-date with new Arduino releases. + +h2. The Basics + +@$ rad my_sketch@ + +This command will create a new RAD project directory (my_sketch/) inside of the current directory that contains a blank script in which you can write your own RAD code, as well as a full install of the RAD support infrastructure (in vendor/). A sample 'hello world' script in RAD will look like this: + +
+class MySketch < ArduinoSketch
+  output_pin 7, :as => :led
+  def loop
+    blink led, 500
+  end
+end
+
+ +Once your code is written, your relevant local configuration properly setup in @config/hardware.rb@ and @config/software.rb@, and an Arduino with the corresponding circuit (a 220ohm resistor and an LED wired in series between Arduino pin 7 and ground) is connected to your computer via serial, run: + +@$ rake make:upload@ + +This will: + + * generate the correct Arduino C++ code from your sketch + * dynamically prepare a localized version of the default Arduino Makefile + * compile your sketch + * prompt you to hit the reset button on your Arduino (if necessary!) + * upload your compiled binary onto your Arduino + +h2. The Arduino API + +With the exception of the still-experimental Serial interface, most of the Arduino software API should be working correctly at this point. Documentation for the Ruby versions of the methods is forthcoming, but it is mostly what you'd expect: methods with identical names and arguments to their Arduino counterparts with the exception of using 'true' and 'false' for HIGH and LOW. + +h2. RAD Needs You! + +All the many discipline-crossing skills required for a project like RAD make for lots of opportunities to help out: Have you written lots of sketches exploring the obscure depths of the Arduino library? Do you run the Arduino development tool chain on an obscure (i.e., non-OS X) platform? Do you develop for other AVR or PIC microcontrollers? Are you a C/C++ ninja? Or even C/C++ competent? + +There's lots to do. + +If you're looking for a place to dive in and don't know quite where, "email Greg":mailto:greg@grabb.it. If you want to start by taking a log at the code, the trunk repository is @svn://rubyforge.org/var/svn/rad/trunk@ for anonymous access. + +h2. License + +This code is free to use under the terms of the GPL 2.0 license, just like the Arduino software library itself. + +h2. Contact + +Comments, questions, heckles, attacks, praises, and, (most especially) patches and contributions are welcome! Send email to "Greg Borenstein":mailto:greg@grabb.it. diff --git a/pkg/rad-0.2.2/website/javascripts/rounded_corners_lite.inc.js b/pkg/rad-0.2.2/website/javascripts/rounded_corners_lite.inc.js new file mode 100644 index 0000000..afc3ea3 --- /dev/null +++ b/pkg/rad-0.2.2/website/javascripts/rounded_corners_lite.inc.js @@ -0,0 +1,285 @@ + + /**************************************************************** + * * + * curvyCorners * + * ------------ * + * * + * This script generates rounded corners for your divs. * + * * + * Version 1.2.9 * + * Copyright (c) 2006 Cameron Cooke * + * By: Cameron Cooke and Tim Hutchison. * + * * + * * + * Website: http://www.curvycorners.net * + * Email: info@totalinfinity.com * + * Forum: http://www.curvycorners.net/forum/ * + * * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU * + * Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will * + * be useful, but WITHOUT ANY WARRANTY; without even the * + * implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU Lesser General Public * + * License for more details. * + * * + * You should have received a copy of the GNU Lesser * + * General Public License along with this library; * + * Inc., 59 Temple Place, Suite 330, Boston, * + * MA 02111-1307 USA * + * * + ****************************************************************/ + +var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1; var isMoz = document.implementation && document.implementation.createDocument; var isSafari = ((navigator.userAgent.toLowerCase().indexOf('safari')!=-1)&&(navigator.userAgent.toLowerCase().indexOf('mac')!=-1))?true:false; function curvyCorners() +{ if(typeof(arguments[0]) != "object") throw newCurvyError("First parameter of curvyCorners() must be an object."); if(typeof(arguments[1]) != "object" && typeof(arguments[1]) != "string") throw newCurvyError("Second parameter of curvyCorners() must be an object or a class name."); if(typeof(arguments[1]) == "string") +{ var startIndex = 0; var boxCol = getElementsByClass(arguments[1]);} +else +{ var startIndex = 1; var boxCol = arguments;} +var curvyCornersCol = new Array(); if(arguments[0].validTags) +var validElements = arguments[0].validTags; else +var validElements = ["div"]; for(var i = startIndex, j = boxCol.length; i < j; i++) +{ var currentTag = boxCol[i].tagName.toLowerCase(); if(inArray(validElements, currentTag) !== false) +{ curvyCornersCol[curvyCornersCol.length] = new curvyObject(arguments[0], boxCol[i]);} +} +this.objects = curvyCornersCol; this.applyCornersToAll = function() +{ for(var x = 0, k = this.objects.length; x < k; x++) +{ this.objects[x].applyCorners();} +} +} +function curvyObject() +{ this.box = arguments[1]; this.settings = arguments[0]; this.topContainer = null; this.bottomContainer = null; this.masterCorners = new Array(); this.contentDIV = null; var boxHeight = get_style(this.box, "height", "height"); var boxWidth = get_style(this.box, "width", "width"); var borderWidth = get_style(this.box, "borderTopWidth", "border-top-width"); var borderColour = get_style(this.box, "borderTopColor", "border-top-color"); var boxColour = get_style(this.box, "backgroundColor", "background-color"); var backgroundImage = get_style(this.box, "backgroundImage", "background-image"); var boxPosition = get_style(this.box, "position", "position"); var boxPadding = get_style(this.box, "paddingTop", "padding-top"); this.boxHeight = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight)); this.boxWidth = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth)); this.borderWidth = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0)); this.boxColour = format_colour(boxColour); this.boxPadding = parseInt(((boxPadding != "" && boxPadding.indexOf("px") !== -1)? boxPadding.slice(0, boxPadding.indexOf("px")) : 0)); this.borderColour = format_colour(borderColour); this.borderString = this.borderWidth + "px" + " solid " + this.borderColour; this.backgroundImage = ((backgroundImage != "none")? backgroundImage : ""); this.boxContent = this.box.innerHTML; if(boxPosition != "absolute") this.box.style.position = "relative"; this.box.style.padding = "0px"; if(isIE && boxWidth == "auto" && boxHeight == "auto") this.box.style.width = "100%"; if(this.settings.autoPad == true && this.boxPadding > 0) +this.box.innerHTML = ""; this.applyCorners = function() +{ for(var t = 0; t < 2; t++) +{ switch(t) +{ case 0: +if(this.settings.tl || this.settings.tr) +{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0); newMainContainer.style.height = topMaxRadius + "px"; newMainContainer.style.top = 0 - topMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.topContainer = this.box.appendChild(newMainContainer);} +break; case 1: +if(this.settings.bl || this.settings.br) +{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0); newMainContainer.style.height = botMaxRadius + "px"; newMainContainer.style.bottom = 0 - botMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.bottomContainer = this.box.appendChild(newMainContainer);} +break;} +} +if(this.topContainer) this.box.style.borderTopWidth = "0px"; if(this.bottomContainer) this.box.style.borderBottomWidth = "0px"; var corners = ["tr", "tl", "br", "bl"]; for(var i in corners) +{ if(i > -1 < 4) +{ var cc = corners[i]; if(!this.settings[cc]) +{ if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null)) +{ var newCorner = document.createElement("DIV"); newCorner.style.position = "relative"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; if(this.backgroundImage == "") +newCorner.style.backgroundColor = this.boxColour; else +newCorner.style.backgroundImage = this.backgroundImage; switch(cc) +{ case "tl": +newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.left = -this.borderWidth + "px"; break; case "tr": +newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.tl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; newCorner.style.left = this.borderWidth + "px"; break; case "bl": +newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.br.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = -this.borderWidth + "px"; newCorner.style.backgroundPosition = "-" + (this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break; case "br": +newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.bl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = this.borderWidth + "px" +newCorner.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break;} +} +} +else +{ if(this.masterCorners[this.settings[cc].radius]) +{ var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);} +else +{ var newCorner = document.createElement("DIV"); newCorner.style.height = this.settings[cc].radius + "px"; newCorner.style.width = this.settings[cc].radius + "px"; newCorner.style.position = "absolute"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth); for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++) +{ if((intx +1) >= borderRadius) +var y1 = -1; else +var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1); if(borderRadius != j) +{ if((intx) >= borderRadius) +var y2 = -1; else +var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2))); if((intx+1) >= j) +var y3 = -1; else +var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);} +if((intx) >= j) +var y4 = -1; else +var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2))); if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius); if(borderRadius != j) +{ for(var inty = (y1 + 1); inty < y2; inty++) +{ if(this.settings.antiAlias) +{ if(this.backgroundImage != "") +{ var borderFract = (pixelFraction(intx, inty, borderRadius) * 100); if(borderFract < 30) +{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);} +else +{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);} +} +else +{ var pixelcolour = BlendColour(this.boxColour, this.borderColour, pixelFraction(intx, inty, borderRadius)); this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius, cc);} +} +} +if(this.settings.antiAlias) +{ if(y3 >= y2) +{ if (y2 == -1) y2 = 0; this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, 0);} +} +else +{ if(y3 >= y1) +{ this.drawPixel(intx, (y1 + 1), this.borderColour, 100, (y3 - y1), newCorner, 0, 0);} +} +var outsideColour = this.borderColour;} +else +{ var outsideColour = this.boxColour; var y3 = y1;} +if(this.settings.antiAlias) +{ for(var inty = (y3 + 1); inty < y4; inty++) +{ this.drawPixel(intx, inty, outsideColour, (pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);} +} +} +this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);} +if(cc != "br") +{ for(var t = 0, k = newCorner.childNodes.length; t < k; t++) +{ var pixelBar = newCorner.childNodes[t]; var pixelBarTop = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px"))); var pixelBarLeft = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px"))); var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px"))); if(cc == "tl" || cc == "bl"){ pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px";} +if(cc == "tr" || cc == "tl"){ pixelBar.style.top = this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px";} +switch(cc) +{ case "tr": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "tl": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "bl": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth) + "px"; break;} +} +} +} +if(newCorner) +{ switch(cc) +{ case "tl": +if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "tr": +if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "bl": +if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break; case "br": +if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break;} +} +} +} +var radiusDiff = new Array(); radiusDiff["t"] = Math.abs(this.settings.tl.radius - this.settings.tr.radius) +radiusDiff["b"] = Math.abs(this.settings.bl.radius - this.settings.br.radius); for(z in radiusDiff) +{ if(z == "t" || z == "b") +{ if(radiusDiff[z]) +{ var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r"); var newFiller = document.createElement("DIV"); newFiller.style.height = radiusDiff[z] + "px"; newFiller.style.width = this.settings[smallerCornerType].radius+ "px" +newFiller.style.position = "absolute"; newFiller.style.fontSize = "1px"; newFiller.style.overflow = "hidden"; newFiller.style.backgroundColor = this.boxColour; switch(smallerCornerType) +{ case "tl": +newFiller.style.bottom = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.topContainer.appendChild(newFiller); break; case "tr": +newFiller.style.bottom = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.topContainer.appendChild(newFiller); break; case "bl": +newFiller.style.top = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.bottomContainer.appendChild(newFiller); break; case "br": +newFiller.style.top = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.bottomContainer.appendChild(newFiller); break;} +} +var newFillerBar = document.createElement("DIV"); newFillerBar.style.position = "relative"; newFillerBar.style.fontSize = "1px"; newFillerBar.style.overflow = "hidden"; newFillerBar.style.backgroundColor = this.boxColour; newFillerBar.style.backgroundImage = this.backgroundImage; switch(z) +{ case "t": +if(this.topContainer) +{ if(this.settings.tl.radius && this.settings.tr.radius) +{ newFillerBar.style.height = topMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.tl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.tr.radius - this.borderWidth + "px"; newFillerBar.style.borderTop = this.borderString; if(this.backgroundImage != "") +newFillerBar.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; this.topContainer.appendChild(newFillerBar);} +this.box.style.backgroundPosition = "0px -" + (topMaxRadius - this.borderWidth) + "px";} +break; case "b": +if(this.bottomContainer) +{ if(this.settings.bl.radius && this.settings.br.radius) +{ newFillerBar.style.height = botMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.bl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.br.radius - this.borderWidth + "px"; newFillerBar.style.borderBottom = this.borderString; if(this.backgroundImage != "") +newFillerBar.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (topMaxRadius + this.borderWidth)) + "px"; this.bottomContainer.appendChild(newFillerBar);} +} +break;} +} +} +if(this.settings.autoPad == true && this.boxPadding > 0) +{ var contentContainer = document.createElement("DIV"); contentContainer.style.position = "relative"; contentContainer.innerHTML = this.boxContent; contentContainer.className = "autoPadDiv"; var topPadding = Math.abs(topMaxRadius - this.boxPadding); var botPadding = Math.abs(botMaxRadius - this.boxPadding); if(topMaxRadius < this.boxPadding) +contentContainer.style.paddingTop = topPadding + "px"; if(botMaxRadius < this.boxPadding) +contentContainer.style.paddingBottom = botMaxRadius + "px"; contentContainer.style.paddingLeft = this.boxPadding + "px"; contentContainer.style.paddingRight = this.boxPadding + "px"; this.contentDIV = this.box.appendChild(contentContainer);} +} +this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius) +{ var pixel = document.createElement("DIV"); pixel.style.height = height + "px"; pixel.style.width = "1px"; pixel.style.position = "absolute"; pixel.style.fontSize = "1px"; pixel.style.overflow = "hidden"; var topMaxRadius = Math.max(this.settings["tr"].radius, this.settings["tl"].radius); if(image == -1 && this.backgroundImage != "") +{ pixel.style.backgroundImage = this.backgroundImage; pixel.style.backgroundPosition = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + topMaxRadius + inty) -this.borderWidth) + "px";} +else +{ pixel.style.backgroundColor = colour;} +if (transAmount != 100) +setOpacity(pixel, transAmount); pixel.style.top = inty + "px"; pixel.style.left = intx + "px"; newCorner.appendChild(pixel);} +} +function insertAfter(parent, node, referenceNode) +{ parent.insertBefore(node, referenceNode.nextSibling);} +function BlendColour(Col1, Col2, Col1Fraction) +{ var red1 = parseInt(Col1.substr(1,2),16); var green1 = parseInt(Col1.substr(3,2),16); var blue1 = parseInt(Col1.substr(5,2),16); var red2 = parseInt(Col2.substr(1,2),16); var green2 = parseInt(Col2.substr(3,2),16); var blue2 = parseInt(Col2.substr(5,2),16); if(Col1Fraction > 1 || Col1Fraction < 0) Col1Fraction = 1; var endRed = Math.round((red1 * Col1Fraction) + (red2 * (1 - Col1Fraction))); if(endRed > 255) endRed = 255; if(endRed < 0) endRed = 0; var endGreen = Math.round((green1 * Col1Fraction) + (green2 * (1 - Col1Fraction))); if(endGreen > 255) endGreen = 255; if(endGreen < 0) endGreen = 0; var endBlue = Math.round((blue1 * Col1Fraction) + (blue2 * (1 - Col1Fraction))); if(endBlue > 255) endBlue = 255; if(endBlue < 0) endBlue = 0; return "#" + IntToHex(endRed)+ IntToHex(endGreen)+ IntToHex(endBlue);} +function IntToHex(strNum) +{ base = strNum / 16; rem = strNum % 16; base = base - (rem / 16); baseS = MakeHex(base); remS = MakeHex(rem); return baseS + '' + remS;} +function MakeHex(x) +{ if((x >= 0) && (x <= 9)) +{ return x;} +else +{ switch(x) +{ case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F";} +} +} +function pixelFraction(x, y, r) +{ var pixelfraction = 0; var xvalues = new Array(1); var yvalues = new Array(1); var point = 0; var whatsides = ""; var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2))); if ((intersect >= y) && (intersect < (y+1))) +{ whatsides = "Left"; xvalues[point] = 0; yvalues[point] = intersect - y; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2))); if ((intersect >= x) && (intersect < (x+1))) +{ whatsides = whatsides + "Top"; xvalues[point] = intersect - x; yvalues[point] = 1; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2))); if ((intersect >= y) && (intersect < (y+1))) +{ whatsides = whatsides + "Right"; xvalues[point] = 1; yvalues[point] = intersect - y; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2))); if ((intersect >= x) && (intersect < (x+1))) +{ whatsides = whatsides + "Bottom"; xvalues[point] = intersect - x; yvalues[point] = 0;} +switch (whatsides) +{ case "LeftRight": +pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2); break; case "TopRight": +pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2); break; case "TopBottom": +pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2); break; case "LeftBottom": +pixelfraction = (yvalues[0]*xvalues[1])/2; break; default: +pixelfraction = 1;} +return pixelfraction;} +function rgb2Hex(rgbColour) +{ try{ var rgbArray = rgb2Array(rgbColour); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); var hexColour = "#" + IntToHex(red) + IntToHex(green) + IntToHex(blue);} +catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");} +return hexColour;} +function rgb2Array(rgbColour) +{ var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")")); var rgbArray = rgbValues.split(", "); return rgbArray;} +function setOpacity(obj, opacity) +{ opacity = (opacity == 100)?99.999:opacity; if(isSafari && obj.tagName != "IFRAME") +{ var rgbArray = rgb2Array(obj.style.backgroundColor); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); obj.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity/100 + ")";} +else if(typeof(obj.style.opacity) != "undefined") +{ obj.style.opacity = opacity/100;} +else if(typeof(obj.style.MozOpacity) != "undefined") +{ obj.style.MozOpacity = opacity/100;} +else if(typeof(obj.style.filter) != "undefined") +{ obj.style.filter = "alpha(opacity:" + opacity + ")";} +else if(typeof(obj.style.KHTMLOpacity) != "undefined") +{ obj.style.KHTMLOpacity = opacity/100;} +} +function inArray(array, value) +{ for(var i = 0; i < array.length; i++){ if (array[i] === value) return i;} +return false;} +function inArrayKey(array, value) +{ for(key in array){ if(key === value) return true;} +return false;} +function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true;} +else if (elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r;} +else { elm['on' + evType] = fn;} +} +function removeEvent(obj, evType, fn, useCapture){ if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true;} else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r;} else { alert("Handler could not be removed");} +} +function format_colour(colour) +{ var returnColour = "#ffffff"; if(colour != "" && colour != "transparent") +{ if(colour.substr(0, 3) == "rgb") +{ returnColour = rgb2Hex(colour);} +else if(colour.length == 4) +{ returnColour = "#" + colour.substring(1, 2) + colour.substring(1, 2) + colour.substring(2, 3) + colour.substring(2, 3) + colour.substring(3, 4) + colour.substring(3, 4);} +else +{ returnColour = colour;} +} +return returnColour;} +function get_style(obj, property, propertyNS) +{ try +{ if(obj.currentStyle) +{ var returnVal = eval("obj.currentStyle." + property);} +else +{ if(isSafari && obj.style.display == "none") +{ obj.style.display = ""; var wasHidden = true;} +var returnVal = document.defaultView.getComputedStyle(obj, '').getPropertyValue(propertyNS); if(isSafari && wasHidden) +{ obj.style.display = "none";} +} +} +catch(e) +{ } +return returnVal;} +function getElementsByClass(searchClass, node, tag) +{ var classElements = new Array(); if(node == null) +node = document; if(tag == null) +tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)"); for (i = 0, j = 0; i < elsLen; i++) +{ if(pattern.test(els[i].className)) +{ classElements[j] = els[i]; j++;} +} +return classElements;} +function newCurvyError(errorMessage) +{ return new Error("curvyCorners Error:\n" + errorMessage) +} diff --git a/pkg/rad-0.2.2/website/stylesheets/screen.css b/pkg/rad-0.2.2/website/stylesheets/screen.css new file mode 100644 index 0000000..b7317cd --- /dev/null +++ b/pkg/rad-0.2.2/website/stylesheets/screen.css @@ -0,0 +1,169 @@ +body { + background-color: #E1D1F1; + font-family: "Georgia", sans-serif; + font-size: 16px; + line-height: 1.6em; + padding: 1.6em 0 0 0; + color: #333; +} +h1, h2, h3, h4, h5, h6 { + color: #444; +} +h1 { + font-family: sans-serif; + font-weight: normal; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; + margin: 5px; +} +li { + padding: 0; + margin: 0; + list-style-type: square; +} +a { + color: #5E5AFF; + background-color: #DAC; + font-weight: normal; + text-decoration: underline; +} +blockquote { + font-size: 90%; + font-style: italic; + border-left: 1px solid #111; + padding-left: 1em; +} + +#buy-arduino { + float:left; + margin-right: 10px; + border: 8px solid #000; + background-color: #fff; + padding: 5px; + text-align:center; +} + +#buy-arduino h4{ + font-size: 12px; + margin: 0; +} + +#buy-arduino a { + background-color: #fff; +} + +#buy-arduino img{ + width: 190px; + border: none; +} + +.caps { + font-size: 80%; +} + +#main { + width: 45em; + padding: 0; + margin: 0 auto; +} +.coda { + text-align: right; + color: #77f; + font-size: smaller; +} + +table { + font-size: 90%; + line-height: 1.4em; + color: #ff8; + background-color: #111; + padding: 2px 10px 2px 10px; + border-style: dashed; +} + +th { + color: #fff; +} + +td { + padding: 2px 10px 2px 10px; +} + +.success { + color: #0CC52B; +} + +.failed { + color: #E90A1B; +} + +.unknown { + color: #995000; +} +pre, code { + font-family: monospace; + font-size: 90%; + line-height: 1.4em; + color: #ff8; + background-color: #111; + padding: 2px 10px 2px 10px; +} +.comment { color: #aaa; font-style: italic; } +.keyword { color: #eff; font-weight: bold; } +.punct { color: #eee; font-weight: bold; } +.symbol { color: #0bb; } +.string { color: #6b4; } +.ident { color: #ff8; } +.constant { color: #66f; } +.regex { color: #ec6; } +.number { color: #F99; } +.expr { color: #227; } + +#metadata { + float: left; + margin-right: 20px; +} + +#version { + width:210px; + text-align: right; + font-family: sans-serif; + font-weight: normal; + background-color: #B3ABFF; + color: #141331; + padding: 15px 20px 10px 20px; + margin: 0 auto; + margin-top: 15px; + border: 3px solid #141331; + margin-bottom: 20px; + +} + +#version .numbers { + display: block; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; + margin-bottom: 15px; +} + +#version p { + text-decoration: none; + color: #141331; + background-color: #B3ABFF; + margin: 0; + padding: 0; +} + +#version a { + text-decoration: none; + color: #141331; + background-color: #B3ABFF; +} + +.clickable { + cursor: pointer; + cursor: hand; +} + diff --git a/pkg/rad-0.2.2/website/template.rhtml b/pkg/rad-0.2.2/website/template.rhtml new file mode 100644 index 0000000..4abf70d --- /dev/null +++ b/pkg/rad-0.2.2/website/template.rhtml @@ -0,0 +1,48 @@ + + + + + + + <%= title %> + + + + + + +
+ +

<%= title %>

+
+

Get Version

+ <%= version %> +
+ <%= body %> +

+ Dr Nic, <%= modified.pretty %>
+ Theme extended from Paul Battley +

+
+ + + + + diff --git a/scripts/txt2html b/scripts/txt2html new file mode 100644 index 0000000..0048857 --- /dev/null +++ b/scripts/txt2html @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'redcloth' +require 'syntax/convertors/html' +require 'erb' +require File.dirname(__FILE__) + '/../lib/rad/version.rb' + +version = Rad::VERSION::STRING +download = 'http://rubyforge.org/projects/rad' + +class Fixnum + def ordinal + # teens + return 'th' if (10..19).include?(self % 100) + # others + case self % 10 + when 1: return 'st' + when 2: return 'nd' + when 3: return 'rd' + else return 'th' + end + end +end + +class Time + def pretty + return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}" + end +end + +def convert_syntax(syntax, source) + return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^
|
$!,'') +end + +if ARGV.length >= 1 + src, template = ARGV + template ||= File.dirname(__FILE__) + '/../website/template.rhtml' + +else + puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html") + exit! +end + +template = ERB.new(File.open(template).read) + +title = nil +body = nil +File.open(src) do |fsrc| + title_text = fsrc.readline + body_text = fsrc.read + syntax_items = [] + body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)!m){ + ident = syntax_items.length + element, syntax, source = $1, $2, $3 + syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}" + "syntax-temp-#{ident}" + } + title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip + body = RedCloth.new(body_text).to_html + body.gsub!(%r!(?:
)?syntax-temp-(d+)(?:
)?!){ syntax_items[$1.to_i] } +end +stat = File.stat(src) +created = stat.ctime +modified = stat.mtime + +$stdout << template.result(binding) diff --git a/setup.rb b/setup.rb new file mode 100644 index 0000000..424a5f3 --- /dev/null +++ b/setup.rb @@ -0,0 +1,1585 @@ +# +# setup.rb +# +# Copyright (c) 2000-2005 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public License version 2.1. +# + +unless Enumerable.method_defined?(:map) # Ruby 1.4.6 + module Enumerable + alias map collect + end +end + +unless File.respond_to?(:read) # Ruby 1.6 + def File.read(fname) + open(fname) {|f| + return f.read + } + end +end + +unless Errno.const_defined?(:ENOTEMPTY) # Windows? + module Errno + class ENOTEMPTY + # We do not raise this exception, implementation is not needed. + end + end +end + +def File.binread(fname) + open(fname, 'rb') {|f| + return f.read + } +end + +# for corrupted Windows' stat(2) +def File.dir?(path) + File.directory?((path[-1,1] == '/') ? path : path + '/') +end + + +class ConfigTable + + include Enumerable + + def initialize(rbconfig) + @rbconfig = rbconfig + @items = [] + @table = {} + # options + @install_prefix = nil + @config_opt = nil + @verbose = true + @no_harm = false + end + + attr_accessor :install_prefix + attr_accessor :config_opt + + attr_writer :verbose + + def verbose? + @verbose + end + + attr_writer :no_harm + + def no_harm? + @no_harm + end + + def [](key) + lookup(key).resolve(self) + end + + def []=(key, val) + lookup(key).set val + end + + def names + @items.map {|i| i.name } + end + + def each(&block) + @items.each(&block) + end + + def key?(name) + @table.key?(name) + end + + def lookup(name) + @table[name] or setup_rb_error "no such config item: #{name}" + end + + def add(item) + @items.push item + @table[item.name] = item + end + + def remove(name) + item = lookup(name) + @items.delete_if {|i| i.name == name } + @table.delete_if {|name, i| i.name == name } + item + end + + def load_script(path, inst = nil) + if File.file?(path) + MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path + end + end + + def savefile + '.config' + end + + def load_savefile + begin + File.foreach(savefile()) do |line| + k, v = *line.split(/=/, 2) + self[k] = v.strip + end + rescue Errno::ENOENT + setup_rb_error $!.message + "\n#{File.basename($0)} config first" + end + end + + def save + @items.each {|i| i.value } + File.open(savefile(), 'w') {|f| + @items.each do |i| + f.printf "%s=%s\n", i.name, i.value if i.value? and i.value + end + } + end + + def load_standard_entries + standard_entries(@rbconfig).each do |ent| + add ent + end + end + + def standard_entries(rbconfig) + c = rbconfig + + rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + if c['rubylibdir'] + # V > 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = c['rubylibdir'] + librubyverarch = c['archdir'] + siteruby = c['sitedir'] + siterubyver = c['sitelibdir'] + siterubyverarch = c['sitearchdir'] + elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = c['sitedir'] + siterubyver = "$siteruby/#{version}" + siterubyverarch = "$siterubyver/#{c['arch']}" + else + # V < 1.4.4 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" + siterubyver = siteruby + siterubyverarch = "$siterubyver/#{c['arch']}" + end + parameterize = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') + } + + if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] + else + makeprog = 'make' + end + + [ + ExecItem.new('installdirs', 'std/site/home', + 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ + {|val, table| + case val + when 'std' + table['rbdir'] = '$librubyver' + table['sodir'] = '$librubyverarch' + when 'site' + table['rbdir'] = '$siterubyver' + table['sodir'] = '$siterubyverarch' + when 'home' + setup_rb_error '$HOME was not set' unless ENV['HOME'] + table['prefix'] = ENV['HOME'] + table['rbdir'] = '$libdir/ruby' + table['sodir'] = '$libdir/ruby' + end + }, + PathItem.new('prefix', 'path', c['prefix'], + 'path prefix of target environment'), + PathItem.new('bindir', 'path', parameterize.call(c['bindir']), + 'the directory for commands'), + PathItem.new('libdir', 'path', parameterize.call(c['libdir']), + 'the directory for libraries'), + PathItem.new('datadir', 'path', parameterize.call(c['datadir']), + 'the directory for shared data'), + PathItem.new('mandir', 'path', parameterize.call(c['mandir']), + 'the directory for man pages'), + PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), + 'the directory for system configuration files'), + PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), + 'the directory for local state data'), + PathItem.new('libruby', 'path', libruby, + 'the directory for ruby libraries'), + PathItem.new('librubyver', 'path', librubyver, + 'the directory for standard ruby libraries'), + PathItem.new('librubyverarch', 'path', librubyverarch, + 'the directory for standard ruby extensions'), + PathItem.new('siteruby', 'path', siteruby, + 'the directory for version-independent aux ruby libraries'), + PathItem.new('siterubyver', 'path', siterubyver, + 'the directory for aux ruby libraries'), + PathItem.new('siterubyverarch', 'path', siterubyverarch, + 'the directory for aux ruby binaries'), + PathItem.new('rbdir', 'path', '$siterubyver', + 'the directory for ruby scripts'), + PathItem.new('sodir', 'path', '$siterubyverarch', + 'the directory for ruby extentions'), + PathItem.new('rubypath', 'path', rubypath, + 'the path to set to #! line'), + ProgramItem.new('rubyprog', 'name', rubypath, + 'the ruby program using for installation'), + ProgramItem.new('makeprog', 'name', makeprog, + 'the make program to compile ruby extentions'), + SelectItem.new('shebang', 'all/ruby/never', 'ruby', + 'shebang line (#!) editing mode'), + BoolItem.new('without-ext', 'yes/no', 'no', + 'does not compile/install ruby extentions') + ] + end + private :standard_entries + + def load_multipackage_entries + multipackage_entries().each do |ent| + add ent + end + end + + def multipackage_entries + [ + PackageSelectionItem.new('with', 'name,name...', '', 'ALL', + 'package names that you want to install'), + PackageSelectionItem.new('without', 'name,name...', '', 'NONE', + 'package names that you do not want to install') + ] + end + private :multipackage_entries + + ALIASES = { + 'std-ruby' => 'librubyver', + 'stdruby' => 'librubyver', + 'rubylibdir' => 'librubyver', + 'archdir' => 'librubyverarch', + 'site-ruby-common' => 'siteruby', # For backward compatibility + 'site-ruby' => 'siterubyver', # For backward compatibility + 'bin-dir' => 'bindir', + 'bin-dir' => 'bindir', + 'rb-dir' => 'rbdir', + 'so-dir' => 'sodir', + 'data-dir' => 'datadir', + 'ruby-path' => 'rubypath', + 'ruby-prog' => 'rubyprog', + 'ruby' => 'rubyprog', + 'make-prog' => 'makeprog', + 'make' => 'makeprog' + } + + def fixup + ALIASES.each do |ali, name| + @table[ali] = @table[name] + end + @items.freeze + @table.freeze + @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ + end + + def parse_opt(opt) + m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" + m.to_a[1,2] + end + + def dllext + @rbconfig['DLEXT'] + end + + def value_config?(name) + lookup(name).value? + end + + class Item + def initialize(name, template, default, desc) + @name = name.freeze + @template = template + @value = default + @default = default + @description = desc + end + + attr_reader :name + attr_reader :description + + attr_accessor :default + alias help_default default + + def help_opt + "--#{@name}=#{@template}" + end + + def value? + true + end + + def value + @value + end + + def resolve(table) + @value.gsub(%r<\$([^/]+)>) { table[$1] } + end + + def set(val) + @value = check(val) + end + + private + + def check(val) + setup_rb_error "config: --#{name} requires argument" unless val + val + end + end + + class BoolItem < Item + def config_type + 'bool' + end + + def help_opt + "--#{@name}" + end + + private + + def check(val) + return 'yes' unless val + case val + when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' + when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' + else + setup_rb_error "config: --#{@name} accepts only yes/no for argument" + end + end + end + + class PathItem < Item + def config_type + 'path' + end + + private + + def check(path) + setup_rb_error "config: --#{@name} requires argument" unless path + path[0,1] == '$' ? path : File.expand_path(path) + end + end + + class ProgramItem < Item + def config_type + 'program' + end + end + + class SelectItem < Item + def initialize(name, selection, default, desc) + super + @ok = selection.split('/') + end + + def config_type + 'select' + end + + private + + def check(val) + unless @ok.include?(val.strip) + setup_rb_error "config: use --#{@name}=#{@template} (#{val})" + end + val.strip + end + end + + class ExecItem < Item + def initialize(name, selection, desc, &block) + super name, selection, nil, desc + @ok = selection.split('/') + @action = block + end + + def config_type + 'exec' + end + + def value? + false + end + + def resolve(table) + setup_rb_error "$#{name()} wrongly used as option value" + end + + undef set + + def evaluate(val, table) + v = val.strip.downcase + unless @ok.include?(v) + setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" + end + @action.call v, table + end + end + + class PackageSelectionItem < Item + def initialize(name, template, default, help_default, desc) + super name, template, default, desc + @help_default = help_default + end + + attr_reader :help_default + + def config_type + 'package' + end + + private + + def check(val) + unless File.dir?("packages/#{val}") + setup_rb_error "config: no such package: #{val}" + end + val + end + end + + class MetaConfigEnvironment + def initialize(config, installer) + @config = config + @installer = installer + end + + def config_names + @config.names + end + + def config?(name) + @config.key?(name) + end + + def bool_config?(name) + @config.lookup(name).config_type == 'bool' + end + + def path_config?(name) + @config.lookup(name).config_type == 'path' + end + + def value_config?(name) + @config.lookup(name).config_type != 'exec' + end + + def add_config(item) + @config.add item + end + + def add_bool_config(name, default, desc) + @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + end + + def add_path_config(name, default, desc) + @config.add PathItem.new(name, 'path', default, desc) + end + + def set_config_default(name, default) + @config.lookup(name).default = default + end + + def remove_config(name) + @config.remove(name) + end + + # For only multipackage + def packages + raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer + @installer.packages + end + + # For only multipackage + def declare_packages(list) + raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer + @installer.packages = list + end + end + +end # class ConfigTable + + +# This module requires: #verbose?, #no_harm? +module FileOperations + + def mkdir_p(dirname, prefix = nil) + dirname = prefix + File.expand_path(dirname) if prefix + $stderr.puts "mkdir -p #{dirname}" if verbose? + return if no_harm? + + # Does not check '/', it's too abnormal. + dirs = File.expand_path(dirname).split(%r<(?=/)>) + if /\A[a-z]:\z/i =~ dirs[0] + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless File.dir?(path) + end + end + + def rm_f(path) + $stderr.puts "rm -f #{path}" if verbose? + return if no_harm? + force_remove_file path + end + + def rm_rf(path) + $stderr.puts "rm -rf #{path}" if verbose? + return if no_harm? + remove_tree path + end + + def remove_tree(path) + if File.symlink?(path) + remove_file path + elsif File.dir?(path) + remove_tree0 path + else + force_remove_file path + end + end + + def remove_tree0(path) + Dir.foreach(path) do |ent| + next if ent == '.' + next if ent == '..' + entpath = "#{path}/#{ent}" + if File.symlink?(entpath) + remove_file entpath + elsif File.dir?(entpath) + remove_tree0 entpath + else + force_remove_file entpath + end + end + begin + Dir.rmdir path + rescue Errno::ENOTEMPTY + # directory may not be empty + end + end + + def move_file(src, dest) + force_remove_file dest + begin + File.rename src, dest + rescue + File.open(dest, 'wb') {|f| + f.write File.binread(src) + } + File.chmod File.stat(src).mode, dest + File.unlink src + end + end + + def force_remove_file(path) + begin + remove_file path + rescue + end + end + + def remove_file(path) + File.chmod 0777, path + File.unlink path + end + + def install(from, dest, mode, prefix = nil) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix ? prefix + File.expand_path(dest) : dest + realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) + str = File.binread(from) + if diff?(str, realdest) + verbose_off { + rm_f realdest if File.exist?(realdest) + } + File.open(realdest, 'wb') {|f| + f.write str + } + File.chmod mode, realdest + + File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| + if prefix + f.puts realdest.sub(prefix, '') + else + f.puts realdest + end + } + end + end + + def diff?(new_content, path) + return true unless File.exist?(path) + new_content != File.binread(path) + end + + def command(*args) + $stderr.puts args.join(' ') if verbose? + system(*args) or raise RuntimeError, + "system(#{args.map{|a| a.inspect }.join(' ')}) failed" + end + + def ruby(*args) + command config('rubyprog'), *args + end + + def make(task = nil) + command(*[config('makeprog'), task].compact) + end + + def extdir?(dir) + File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") + end + + def files_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.file?("#{dir}/#{ent}") } + } + end + + DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) + + def directories_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT + } + end + +end + + +# This module requires: #srcdir_root, #objdir_root, #relpath +module HookScriptAPI + + def get_config(key) + @config[key] + end + + alias config get_config + + # obsolete: use metaconfig to change configuration + def set_config(key, val) + @config[key] = val + end + + # + # srcdir/objdir (works only in the package directory) + # + + def curr_srcdir + "#{srcdir_root()}/#{relpath()}" + end + + def curr_objdir + "#{objdir_root()}/#{relpath()}" + end + + def srcfile(path) + "#{curr_srcdir()}/#{path}" + end + + def srcexist?(path) + File.exist?(srcfile(path)) + end + + def srcdirectory?(path) + File.dir?(srcfile(path)) + end + + def srcfile?(path) + File.file?(srcfile(path)) + end + + def srcentries(path = '.') + Dir.open("#{curr_srcdir()}/#{path}") {|d| + return d.to_a - %w(. ..) + } + end + + def srcfiles(path = '.') + srcentries(path).select {|fname| + File.file?(File.join(curr_srcdir(), path, fname)) + } + end + + def srcdirectories(path = '.') + srcentries(path).select {|fname| + File.dir?(File.join(curr_srcdir(), path, fname)) + } + end + +end + + +class ToplevelInstaller + + Version = '3.4.1' + Copyright = 'Copyright (c) 2000-2005 Minero Aoki' + + TASKS = [ + [ 'all', 'do config, setup, then install' ], + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles ruby extentions and others' ], + [ 'install', 'installs files' ], + [ 'test', 'run all tests in test/' ], + [ 'clean', "does `make clean' for each extention" ], + [ 'distclean',"does `make distclean' for each extention" ] + ] + + def ToplevelInstaller.invoke + config = ConfigTable.new(load_rbconfig()) + config.load_standard_entries + config.load_multipackage_entries if multipackage? + config.fixup + klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) + klass.new(File.dirname($0), config).invoke + end + + def ToplevelInstaller.multipackage? + File.dir?(File.dirname($0) + '/packages') + end + + def ToplevelInstaller.load_rbconfig + if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + load File.expand_path(arg.split(/=/, 2)[1]) + $".push 'rbconfig.rb' + else + require 'rbconfig' + end + ::Config::CONFIG + end + + def initialize(ardir_root, config) + @ardir = File.expand_path(ardir_root) + @config = config + # cache + @valid_task_re = nil + end + + def config(key) + @config[key] + end + + def inspect + "#<#{self.class} #{__id__()}>" + end + + def invoke + run_metaconfigs + case task = parsearg_global() + when nil, 'all' + parsearg_config + init_installers + exec_config + exec_setup + exec_install + else + case task + when 'config', 'test' + ; + when 'clean', 'distclean' + @config.load_savefile if File.exist?(@config.savefile) + else + @config.load_savefile + end + __send__ "parsearg_#{task}" + init_installers + __send__ "exec_#{task}" + end + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig" + end + + def init_installers + @installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + # + # Hook Script API bases + # + + def srcdir_root + @ardir + end + + def objdir_root + '.' + end + + def relpath + '.' + end + + # + # Option Parsing + # + + def parsearg_global + while arg = ARGV.shift + case arg + when /\A\w+\z/ + setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) + return arg + when '-q', '--quiet' + @config.verbose = false + when '--verbose' + @config.verbose = true + when '--help' + print_usage $stdout + exit 0 + when '--version' + puts "#{File.basename($0)} version #{Version}" + exit 0 + when '--copyright' + puts Copyright + exit 0 + else + setup_rb_error "unknown global option '#{arg}'" + end + end + nil + end + + def valid_task?(t) + valid_task_re() =~ t + end + + def valid_task_re + @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ + end + + def parsearg_no_options + unless ARGV.empty? + task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) + setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" + end + end + + alias parsearg_show parsearg_no_options + alias parsearg_setup parsearg_no_options + alias parsearg_test parsearg_no_options + alias parsearg_clean parsearg_no_options + alias parsearg_distclean parsearg_no_options + + def parsearg_config + evalopt = [] + set = [] + @config.config_opt = [] + while i = ARGV.shift + if /\A--?\z/ =~ i + @config.config_opt = ARGV.dup + break + end + name, value = *@config.parse_opt(i) + if @config.value_config?(name) + @config[name] = value + else + evalopt.push [name, value] + end + set.push name + end + evalopt.each do |name, value| + @config.lookup(name).evaluate value, @config + end + # Check if configuration is valid + set.each do |n| + @config[n] if @config.value_config?(n) + end + end + + def parsearg_install + @config.no_harm = false + @config.install_prefix = '' + while a = ARGV.shift + case a + when '--no-harm' + @config.no_harm = true + when /\A--prefix=/ + path = a.split(/=/, 2)[1] + path = File.expand_path(path) unless path[0,1] == '/' + @config.install_prefix = path + else + setup_rb_error "install: unknown option #{a}" + end + end + end + + def print_usage(out) + out.puts 'Typical Installation Procedure:' + out.puts " $ ruby #{File.basename $0} config" + out.puts " $ ruby #{File.basename $0} setup" + out.puts " # ruby #{File.basename $0} install (may require root privilege)" + out.puts + out.puts 'Detailed Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-24s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, ' --help', 'print this message' + out.printf fmt, ' --version', 'print version and quit' + out.printf fmt, ' --copyright', 'print copyright and quit' + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf fmt, name, desc + end + + fmt = " %-24s %s [%s]\n" + out.puts + out.puts 'Options for CONFIG or ALL:' + @config.each do |item| + out.printf fmt, item.help_opt, item.description, item.help_default + end + out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" + out.puts + out.puts 'Options for INSTALL:' + out.printf fmt, '--no-harm', 'only display what to do if given', 'off' + out.printf fmt, '--prefix=path', 'install path prefix', '' + out.puts + end + + # + # Task Handlers + # + + def exec_config + @installer.exec_config + @config.save # must be final + end + + def exec_setup + @installer.exec_setup + end + + def exec_install + @installer.exec_install + end + + def exec_test + @installer.exec_test + end + + def exec_show + @config.each do |i| + printf "%-20s %s\n", i.name, i.value if i.value? + end + end + + def exec_clean + @installer.exec_clean + end + + def exec_distclean + @installer.exec_distclean + end + +end # class ToplevelInstaller + + +class ToplevelInstallerMulti < ToplevelInstaller + + include FileOperations + + def initialize(ardir_root, config) + super + @packages = directories_of("#{@ardir}/packages") + raise 'no package exists' if @packages.empty? + @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig", self + @packages.each do |name| + @config.load_script "#{@ardir}/packages/#{name}/metaconfig" + end + end + + attr_reader :packages + + def packages=(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + + def init_installers + @installers = {} + @packages.each do |pack| + @installers[pack] = Installer.new(@config, + "#{@ardir}/packages/#{pack}", + "packages/#{pack}") + end + with = extract_selection(config('with')) + without = extract_selection(config('without')) + @selected = @installers.keys.select {|name| + (with.empty? or with.include?(name)) \ + and not without.include?(name) + } + end + + def extract_selection(list) + a = list.split(/,/) + a.each do |name| + setup_rb_error "no such package: #{name}" unless @installers.key?(name) + end + a + end + + def print_usage(f) + super + f.puts 'Inluded packages:' + f.puts ' ' + @packages.sort.join(' ') + f.puts + end + + # + # Task Handlers + # + + def exec_config + run_hook 'pre-config' + each_selected_installers {|inst| inst.exec_config } + run_hook 'post-config' + @config.save # must be final + end + + def exec_setup + run_hook 'pre-setup' + each_selected_installers {|inst| inst.exec_setup } + run_hook 'post-setup' + end + + def exec_install + run_hook 'pre-install' + each_selected_installers {|inst| inst.exec_install } + run_hook 'post-install' + end + + def exec_test + run_hook 'pre-test' + each_selected_installers {|inst| inst.exec_test } + run_hook 'post-test' + end + + def exec_clean + rm_f @config.savefile + run_hook 'pre-clean' + each_selected_installers {|inst| inst.exec_clean } + run_hook 'post-clean' + end + + def exec_distclean + rm_f @config.savefile + run_hook 'pre-distclean' + each_selected_installers {|inst| inst.exec_distclean } + run_hook 'post-distclean' + end + + # + # lib + # + + def each_selected_installers + Dir.mkdir 'packages' unless File.dir?('packages') + @selected.each do |pack| + $stderr.puts "Processing the package `#{pack}' ..." if verbose? + Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") + Dir.chdir "packages/#{pack}" + yield @installers[pack] + Dir.chdir '../..' + end + end + + def run_hook(id) + @root_installer.run_hook id + end + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + +end # class ToplevelInstallerMulti + + +class Installer + + FILETYPES = %w( bin lib ext data conf man ) + + include FileOperations + include HookScriptAPI + + def initialize(config, srcroot, objroot) + @config = config + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{self.class} #{File.basename(@srcdir)}>" + end + + def noop(rel) + end + + # + # Hook Script API base methods + # + + def srcdir_root + @srcdir + end + + def objdir_root + @objdir + end + + def relpath + @currdir + end + + # + # Config Access + # + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + + def verbose_off + begin + save, @config.verbose = @config.verbose?, false + yield + ensure + @config.verbose = save + end + end + + # + # TASK config + # + + def exec_config + exec_task_traverse 'config' + end + + alias config_dir_bin noop + alias config_dir_lib noop + + def config_dir_ext(rel) + extconf if extdir?(curr_srcdir()) + end + + alias config_dir_data noop + alias config_dir_conf noop + alias config_dir_man noop + + def extconf + ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt + end + + # + # TASK setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin(rel) + files_of(curr_srcdir()).each do |fname| + update_shebang_line "#{curr_srcdir()}/#{fname}" + end + end + + alias setup_dir_lib noop + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + alias setup_dir_data noop + alias setup_dir_conf noop + alias setup_dir_man noop + + def update_shebang_line(path) + return if no_harm? + return if config('shebang') == 'never' + old = Shebang.load(path) + if old + $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 + new = new_shebang(old) + return if new.to_s == old.to_s + else + return unless config('shebang') == 'all' + new = Shebang.new(config('rubypath')) + end + $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? + open_atomic_writer(path) {|output| + File.open(path, 'rb') {|f| + f.gets if old # discard + output.puts new.to_s + output.print f.read + } + } + end + + def new_shebang(old) + if /\Aruby/ =~ File.basename(old.cmd) + Shebang.new(config('rubypath'), old.args) + elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' + Shebang.new(config('rubypath'), old.args[1..-1]) + else + return old unless config('shebang') == 'all' + Shebang.new(config('rubypath')) + end + end + + def open_atomic_writer(path, &block) + tmpfile = File.basename(path) + '.tmp' + begin + File.open(tmpfile, 'wb', &block) + File.rename tmpfile, File.basename(path) + ensure + File.unlink tmpfile if File.exist?(tmpfile) + end + end + + class Shebang + def Shebang.load(path) + line = nil + File.open(path) {|f| + line = f.gets + } + return nil unless /\A#!/ =~ line + parse(line) + end + + def Shebang.parse(line) + cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') + new(cmd, args) + end + + def initialize(cmd, args = []) + @cmd = cmd + @args = args + end + + attr_reader :cmd + attr_reader :args + + def to_s + "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") + end + end + + # + # TASK install + # + + def exec_install + rm_f 'InstalledFiles' + exec_task_traverse 'install' + end + + def install_dir_bin(rel) + install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 + end + + def install_dir_lib(rel) + install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 + end + + def install_dir_ext(rel) + return unless extdir?(curr_srcdir()) + install_files rubyextentions('.'), + "#{config('sodir')}/#{File.dirname(rel)}", + 0555 + end + + def install_dir_data(rel) + install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 + end + + def install_dir_conf(rel) + # FIXME: should not remove current config files + # (rename previous file to .old/.org) + install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 + end + + def install_dir_man(rel) + install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 + end + + def install_files(list, dest, mode) + mkdir_p dest, @config.install_prefix + list.each do |fname| + install fname, dest, mode, @config.install_prefix + end + end + + def libfiles + glob_reject(%w(*.y *.output), targetfiles()) + end + + def rubyextentions(dir) + ents = glob_select("*.#{@config.dllext}", targetfiles()) + if ents.empty? + setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" + end + ents + end + + def targetfiles + mapdir(existfiles() - hookfiles()) + end + + def mapdir(ents) + ents.map {|ent| + if File.exist?(ent) + then ent # objdir + else "#{curr_srcdir()}/#{ent}" # srcdir + end + } + end + + # picked up many entries from cvs-1.11.1/src/ignore.c + JUNK_FILES = %w( + core RCSLOG tags TAGS .make.state + .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb + *~ *.old *.bak *.BAK *.orig *.rej _$* *$ + + *.org *.in .* + ) + + def existfiles + glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) + end + + def hookfiles + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| + %w( config setup install clean ).map {|t| sprintf(fmt, t) } + }.flatten + end + + def glob_select(pat, ents) + re = globs2re([pat]) + ents.select {|ent| re =~ ent } + end + + def glob_reject(pats, ents) + re = globs2re(pats) + ents.reject {|ent| re =~ ent } + end + + GLOB2REGEX = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + + def globs2re(pats) + /\A(?:#{ + pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') + })\z/ + end + + # + # TASK test + # + + TESTDIR = 'test' + + def exec_test + unless File.directory?('test') + $stderr.puts 'no test in this package' if verbose? + return + end + $stderr.puts 'Running tests...' if verbose? + begin + require 'test/unit' + rescue LoadError + setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' + end + runner = Test::Unit::AutoRunner.new(true) + runner.to_run << TESTDIR + runner.run + end + + # + # TASK clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias clean_dir_bin noop + alias clean_dir_lib noop + alias clean_dir_data noop + alias clean_dir_conf noop + alias clean_dir_man noop + + def clean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'clean' if File.file?('Makefile') + end + + # + # TASK distclean + # + + def exec_distclean + exec_task_traverse 'distclean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias distclean_dir_bin noop + alias distclean_dir_lib noop + + def distclean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'distclean' if File.file?('Makefile') + end + + alias distclean_dir_data noop + alias distclean_dir_conf noop + alias distclean_dir_man noop + + # + # Traversing + # + + def exec_task_traverse(task) + run_hook "pre-#{task}" + FILETYPES.each do |type| + if type == 'ext' and config('without-ext') == 'yes' + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, "#{task}_dir_#{type}" + end + run_hook "post-#{task}" + end + + def traverse(task, rel, mid) + dive_into(rel) { + run_hook "pre-#{task}" + __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') + directories_of(curr_srcdir()).each do |d| + traverse task, "#{rel}/#{d}", mid + end + run_hook "post-#{task}" + } + end + + def dive_into(rel) + return unless File.dir?("#{@srcdir}/#{rel}") + + dir = File.basename(rel) + Dir.mkdir dir unless File.dir?(dir) + prevdir = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir prevdir + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + + def run_hook(id) + path = [ "#{curr_srcdir()}/#{id}", + "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } + return unless path + begin + instance_eval File.read(path), path, 1 + rescue + raise if $DEBUG + setup_rb_error "hook #{path} failed:\n" + $!.message + end + end + +end # class Installer + + +class SetupError < StandardError; end + +def setup_rb_error(msg) + raise SetupError, msg +end + +if $0 == __FILE__ + begin + ToplevelInstaller.invoke + rescue SetupError + raise if $DEBUG + $stderr.puts $!.message + $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." + exit 1 + end +end diff --git a/spec/examples/hello_world.rb b/spec/examples/hello_world.rb new file mode 100644 index 0000000..cb48324 --- /dev/null +++ b/spec/examples/hello_world.rb @@ -0,0 +1,11 @@ +# Hardware: LED connected on pin 7 + +class HelloWorld < ArduinoSketch + output_pin 7, :as => :led + def loop + digitalWrite led, ON + delay 500 + digitalWrite led, OFF + delay 500 + end +end diff --git a/spec/examples/hello_world.rb~ b/spec/examples/hello_world.rb~ new file mode 100644 index 0000000..231d558 --- /dev/null +++ b/spec/examples/hello_world.rb~ @@ -0,0 +1,8 @@ +# Hardware: LED connected on pin 7 + +class HelloWorld < ArduinoSketch + output_pin 7, :as => :led + def loop + blink led, 500 + end +end diff --git a/spec/examples/serial_motor.rb b/spec/examples/serial_motor.rb new file mode 100644 index 0000000..bbbccca --- /dev/null +++ b/spec/examples/serial_motor.rb @@ -0,0 +1,12 @@ +# Hardware: motor control circuit (i.e. TIP-120 control pin) +# connected at pin 7. +# Demo: http://www.youtube.com/watch?v=7OguEBfdTe0 + +class SerialMotor < ArduinoSketch + output_pin 7, :as => :motor + serial_begin + + def loop + digitalWrite(motor, serial_read) if serial_available + end +end diff --git a/spec/models/arduino_sketch_spec.rb b/spec/models/arduino_sketch_spec.rb new file mode 100644 index 0000000..e33b4a6 --- /dev/null +++ b/spec/models/arduino_sketch_spec.rb @@ -0,0 +1,82 @@ +require File.dirname(__FILE__) + '/spec_helper.rb' +require File.expand_path(File.dirname(__FILE__) + "/../../lib/rad/arduino_sketch.rb") + +context "Arduino#serial_begin" do + setup do + @as = ArduinoSketch.new + end + + specify "should default baud_rate to 9600" do + @as.serial_begin + @as.instance_variable_get("@other_setup").should include("Serial.begin(9600);") + end + specify "should set an alternate baud_rate if told" do + @as.serial_begin :rate => 2400 + @as.instance_variable_get("@other_setup").should include("Serial.begin(2400);") + end + specify "should add the correct function call to the composed_setup" do + @as.serial_begin + @as.compose_setup.should match(Regexp.new(Regexp.escape("Serial.begin(9600);"))) + end +end + + +context "Arduino Base" do + setup do + @as = ArduinoSketch.new + end + + specify "output_pin method without :as arg. should add the pin to the pin_mode hash's output list and leave the declarations alone" do + @as.output_pin 1 + @as.instance_variable_get("@declarations").first.should == nil + @as.instance_variable_get("@pin_modes")[:output].should include(1) + end + + specify "output_pin method with :as arg. should add the pin to the pin_mode hash's output list write the appropriate declaration and accessor" do + @as.output_pin 3, :as => :led + @as.instance_variable_get("@declarations").first.should == "int _led = 3;" + @as.instance_variable_get("@accessors").first.should == "int led(){\nreturn _led;\n}" + @as.instance_variable_get("@pin_modes")[:output].should include(3) + end + + specify "output_pins method should add the pin to the pin_mode hash's output list and leave the declarations and accessors alone" do + @as.output_pins [5,4,3,2] + @as.instance_variable_get("@pin_modes")[:output].should include(5) + @as.instance_variable_get("@pin_modes")[:output].should include(4) + @as.instance_variable_get("@pin_modes")[:output].should include(3) + @as.instance_variable_get("@pin_modes")[:output].should include(2) + @as.instance_variable_get("@declarations").first.should == nil + @as.instance_variable_get("@accessors").first.should == nil + end + + specify "input_pin method with :as arg. should add the pin to the pin_mode hash's input list write the appropriate declaration and accessor" do + @as.input_pin 1, :as => :knob + @as.instance_variable_get("@declarations").first.should == "int _knob = 1;" + @as.instance_variable_get("@accessors").first.should == "int knob(){\nreturn _knob;\n}" + @as.instance_variable_get("@pin_modes")[:input].should include(1) + end + + specify "input_pins method should add the pins to the pin_mode hash's input list and leave the declarations and accessors alone" do + @as.input_pins [5,4,3,2] + @as.instance_variable_get("@pin_modes")[:input].should include(5) + @as.instance_variable_get("@pin_modes")[:input].should include(4) + @as.instance_variable_get("@pin_modes")[:input].should include(3) + @as.instance_variable_get("@pin_modes")[:input].should include(2) + @as.instance_variable_get("@declarations").first.should == nil + @as.instance_variable_get("@accessors").first.should == nil + end + + specify "compose_setup should append each appropriate pinMode statement and :as accessor to the setup_function string with a newline" do + @as.output_pins [1, 2] + @as.input_pin 3, :as => :button + + result = @as.send :compose_setup + + result.should match(Regexp.new(Regexp.escape("pinMode(1, OUTPUT);\n"))) + result.should match(Regexp.new(Regexp.escape("pinMode(2, OUTPUT);\n"))) + result.should match(Regexp.new(Regexp.escape("pinMode(3, INPUT);\n"))) + result.should match(Regexp.new(Regexp.escape("int _button = 3;\n"))) + result.should match(Regexp.new(Regexp.escape("int button(){\nreturn _button;\n}"))) + end + +end \ No newline at end of file diff --git a/spec/models/spec_helper.rb b/spec/models/spec_helper.rb new file mode 100644 index 0000000..2660e27 --- /dev/null +++ b/spec/models/spec_helper.rb @@ -0,0 +1,2 @@ +require 'rubygems' +require 'spec' diff --git a/spec/sim/hello_world_spec.rb b/spec/sim/hello_world_spec.rb new file mode 100644 index 0000000..defdf2b --- /dev/null +++ b/spec/sim/hello_world_spec.rb @@ -0,0 +1,42 @@ +require File.dirname(__FILE__) + './models/spec_helper.rb' +require File.expand_path(File.dirname(__FILE__) + "/../../lib/rad/sim/arduino_sketch.rb") +require File.expand_path(File.dirname(__FILE__) + "/../examples/hello_world.rb" ) + +context "ArduinoSketch running HelloWorld example" do + it "should successfully make an instance" do + lambda{HelloWorld.new}.should_not raise_error + end +end + +context "HelloWorld#led" do + it "should return a correctly configured Pin" do + p = HelloWorld.new.led + p.type.should == :output + p.num.should == 7 + p.value.should == false + end +end + +context "HelloWorld#digitalWrite" do + setup do + @h = HelloWorld.new + end + + it "should set the value of the pin to true if told to" do + @h.digitalWrite(@h.led, ON) + @h.led.value.should == true + end + + it "should set the value of the pin to false if told to" do + @h.digitalWrite(@h.led, OFF) + @h.led.value.should == false + end +end + +context "HelloWorld#delay" do + it "should maybe keep track of the time in some way?" +end + +context "HelloWorld#loop" do + it "should execute the loop in the context of the instance" +end diff --git a/spec/sim/hello_world_spec.rb~ b/spec/sim/hello_world_spec.rb~ new file mode 100644 index 0000000..3fee6a3 --- /dev/null +++ b/spec/sim/hello_world_spec.rb~ @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/spec_helper.rb' +require File.expand_path(File.dirname(__FILE__) + "/../../lib/rad/sim/arduino_sketch.rb") + +context "ArduinoSketch" do + setup do + + end +end diff --git a/spec/spec.opts b/spec/spec.opts new file mode 100644 index 0000000..cf6add7 --- /dev/null +++ b/spec/spec.opts @@ -0,0 +1 @@ +--colour \ No newline at end of file diff --git a/website/examples/assembler_test.rb.html b/website/examples/assembler_test.rb.html new file mode 100644 index 0000000..92232d0 --- /dev/null +++ b/website/examples/assembler_test.rb.html @@ -0,0 +1,73 @@ + + + assembler_test.rb.html + + .ruby .normal {} + .ruby .comment { color: #888; font-style: italic; } + .ruby .keyword { color: #A00; font-weight: bold; } + .ruby .method { color: #077; } + .ruby .class { color: #074; } + .ruby .module { color: #050; } + .ruby .punct { color: #447; font-weight: bold; } + .ruby .symbol { color: #099; } + .ruby .string { color: #944; } + .ruby .char { color: #F07; } + .ruby .ident { color: #004; } + .ruby .constant { color: #07F; } + .ruby .regex { color: #B66; } + .ruby .number { color: #D55; } + .ruby .attribute { color: #377; } + .ruby .global { color: #3B7; } + .ruby .expr { color: #227; } + + + +
+
# Hardware: Connect to serial output with screen:
+#           $ screen /dev/tty/path.to.your.usb 9600
+
+class AssemblerTest < ArduinoSketch
+  vars :a => 10, :b => 4
+  serial_begin
+  
+  def loop
+    serial_println product(a,b)
+  end
+  
+  assembler( :product, "int product(int a, int b);",
+    <<-CODE
+    product:
+      	mov  r18,r24	; move a to another register
+      	ldi  r24,0		; clear running sum, used to coalesce product
+      	ldi  r25,0		; sum = 0
+      
+      .loop:
+      	tst  r18		  ; is a = 0? if so, we're done
+      	breq .end
+      
+      	mov  r19,r18	; copy a
+      	andi r19,1		; is a % 2 == 0
+      	breq .skip		
+      
+      	add  r24,r22	; add b to sum
+      	adc  r25,r23
+      
+      .skip:
+      	lsr  r18		  ; divide a by 2
+      
+      	clc			
+      	rol  r22		  ; multiply b by 2
+      	rol  r23
+      	rjmp .loop
+      
+      .end:
+      	ret
+      	.size product, .-product
+    CODE
+  )
+end
+
+
+ + diff --git a/website/examples/gps_reader.rb.html b/website/examples/gps_reader.rb.html new file mode 100644 index 0000000..9a30b88 --- /dev/null +++ b/website/examples/gps_reader.rb.html @@ -0,0 +1,39 @@ + + + gps_reader.rb.html + + .ruby .normal {} + .ruby .comment { color: #888; font-style: italic; } + .ruby .keyword { color: #A00; font-weight: bold; } + .ruby .method { color: #077; } + .ruby .class { color: #074; } + .ruby .module { color: #050; } + .ruby .punct { color: #447; font-weight: bold; } + .ruby .symbol { color: #099; } + .ruby .string { color: #944; } + .ruby .char { color: #F07; } + .ruby .ident { color: #004; } + .ruby .constant { color: #07F; } + .ruby .regex { color: #B66; } + .ruby .number { color: #D55; } + .ruby .attribute { color: #377; } + .ruby .global { color: #3B7; } + .ruby .expr { color: #227; } + + + +
+
class GpsReader < ArduinoSketch
+  output_pin 13, :as => :led
+  software_serial 6, 7, :as => :gps
+  serial_begin
+
+  def loop
+    digitalWrite(led, true)
+    serial_print(gps.read)
+  end
+end
+
+ + diff --git a/website/examples/hello_world.rb.html b/website/examples/hello_world.rb.html new file mode 100644 index 0000000..80b47a2 --- /dev/null +++ b/website/examples/hello_world.rb.html @@ -0,0 +1,38 @@ + + + hello_world.rb.html + + .ruby .normal {} + .ruby .comment { color: #888; font-style: italic; } + .ruby .keyword { color: #A00; font-weight: bold; } + .ruby .method { color: #077; } + .ruby .class { color: #074; } + .ruby .module { color: #050; } + .ruby .punct { color: #447; font-weight: bold; } + .ruby .symbol { color: #099; } + .ruby .string { color: #944; } + .ruby .char { color: #F07; } + .ruby .ident { color: #004; } + .ruby .constant { color: #07F; } + .ruby .regex { color: #B66; } + .ruby .number { color: #D55; } + .ruby .attribute { color: #377; } + .ruby .global { color: #3B7; } + .ruby .expr { color: #227; } + + + +
+
# Hardware: LED connected on pin 7
+
+class HelloWorld < ArduinoSketch
+  output_pin 7, :as => :led
+  def loop
+    blink led, 500
+  end
+end
+
+
+ + diff --git a/website/examples/serial_motor.rb.html b/website/examples/serial_motor.rb.html new file mode 100644 index 0000000..8dfd891 --- /dev/null +++ b/website/examples/serial_motor.rb.html @@ -0,0 +1,41 @@ + + + serial_motor.rb.html + + .ruby .normal {} + .ruby .comment { color: #888; font-style: italic; } + .ruby .keyword { color: #A00; font-weight: bold; } + .ruby .method { color: #077; } + .ruby .class { color: #074; } + .ruby .module { color: #050; } + .ruby .punct { color: #447; font-weight: bold; } + .ruby .symbol { color: #099; } + .ruby .string { color: #944; } + .ruby .char { color: #F07; } + .ruby .ident { color: #004; } + .ruby .constant { color: #07F; } + .ruby .regex { color: #B66; } + .ruby .number { color: #D55; } + .ruby .attribute { color: #377; } + .ruby .global { color: #3B7; } + .ruby .expr { color: #227; } + + + +
+
# Hardware: motor control circuit (i.e. TIP-120 control pin)
+#           connected at pin 7.
+#     Demo: http://www.youtube.com/watch?v=7OguEBfdTe0
+
+class SerialMotor < ArduinoSketch
+  output_pin 7, :as => :motor
+  serial_begin
+  
+  def loop
+    digitalWrite(motor, serial_read) if serial_available
+  end
+end
+
+ + diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..fed33ce --- /dev/null +++ b/website/index.html @@ -0,0 +1,177 @@ + + + + + + + RAD + + + + + + +
+
+
+

Get Version

+ 0.2.2 +
+ +
+

RAD

+

→ ‘Ruby Arduino Development’

+ + + + +

What?

+ + +

RAD is a framework for programming the Arduino physcial computing platform using Ruby. RAD converts Ruby scripts written using a set of Rails-like conventions and helpers into C source code which can be compiled and run on the Arduino microcontroller. It also provides a set of Rake tasks for automating the compilation and upload process.

+ +
+ +

Demo: 'Hello World'

+ +

Here's a basic demo of RAD in action. In this movie, we'll write, compile, and upload the universal physical computing 'Hello World': a single flashing LED. +

+ Note: This movie was made using an old version of the Arduino board which required a hardware reset before being able to accept a new sketch. More recent versions of the board don't have this requirement and hence as of version 0.2.0, RAD no longer prompts for reset when running 'rake make:upload' (thought the option is still available for older boards.) +

+
+

Why?

+ + +

While duplicating the functionality of the well-designed Arduino software interface in Ruby may seem like an odd or redundant goal, RAD has further ambitions! Bootstrapping the ability to write microcontroller code in a high level dynamic language like Ruby could greatly ease the creation of all the luxurious development aids the users of such a language have come to expect: developer testing, platform independence, easy metaprogramming, etc.

+ + +

Installing

+ + +

$ sudo gem install rad

+ + +

You’ll also need to have the Arduino environment installed, which you can get from the Arduino website. RAD currently requires Arduino 0010, but we try to keep it up-to-date with new Arduino releases.

+ + +

The Basics

+ + +

$ rad my_sketch

+ + +

This command will create a new RAD project directory (my_sketch/) inside of the current directory that contains a blank script in which you can write your own RAD code, as well as a full install of the RAD support infrastructure (in vendor/). A sample ‘hello world’ script in RAD will look like this:

+ + +
+class MySketch < ArduinoSketch
+  output_pin 7, :as => :led
+  def loop
+    blink led, 500
+  end
+end
+
+ +

Once your code is written, your relevant local configuration properly setup in config/hardware.rb and config/software.rb, and an Arduino with the corresponding circuit (a 220ohm resistor and an LED wired in series between Arduino pin 7 and ground) is connected to your computer via serial, run:

+ + +

$ rake make:upload

+ + +

This will:

+ + +
    +
  • generate the correct Arduino C++ code from your sketch
  • +
  • dynamically prepare a localized version of the default Arduino Makefile
  • +
  • compile your sketch
  • +
  • prompt you to hit the reset button on your Arduino (if necessary!)
  • +
  • upload your compiled binary onto your Arduino
  • +
+ + +

Documentation and The Arduino API

+ + +

Most of the Arduino software API should be working correctly at this point. Documentation for RAD's version of things and details about usage of the wider Arduino API are available in the RAD rDocs.

+ +

Demo: Serial Communication

+

+ To demonstrate some of the more advanced features of RAD, here's a movie showing how to program the Arduino to listen to serial communication from a computer. +

+
+ + Note: The same comment from above applies here about the hardware reset. Also, extra points are available if you recognize the logo on the flag in the video. +

+
+ +

For more examples of RAD in action, see the RAD example directory.

+

RAD Needs You!

+ + +

All the many discipline-crossing skills required for a project like RAD make for lots of opportunities to help out: Have you written lots of sketches exploring the obscure depths of the Arduino library? Do you run the Arduino development tool chain on an obscure (i.e., non-OS X) platform? Do you develop for other AVR or PIC microcontrollers? Are you a C/C++ ninja? Or even C/C++ competent?

+ + +

There’s lots to do.

+ + +

If you’re looking for a place to dive in and don’t know quite where, email the RAD Google Group; we're friendly! If you want to start by taking a log at the code, the trunk repository is svn://rubyforge.org/var/svn/rad/trunk for anonymous access.

+ + +

License

+ + +

This code is free to use under the terms of the GPL 2.0 license, just like the Arduino software library itself.

+ + +

Contact

+ + +

Comments, questions, heckles, attacks, praises, and, (most especially) patches and contributions are welcome! Send email to the RAD mailing list.

+ +

Who

+

Greg Borenstein is RAD's original author and main maintainer with significant contributions from Ben Bleything and Brian Riley, patches from Scott Windsor and David Michael, and the support of the the Ruby Arduino Development Google Group.

+ +

+ Dr Nic, 18th November 2007
+ Theme extended from Paul Battley +

+
+ + + + + + diff --git a/website/index.txt b/website/index.txt new file mode 100644 index 0000000..199bb82 --- /dev/null +++ b/website/index.txt @@ -0,0 +1,64 @@ +h1. RAD + +h1. → 'Ruby Arduino Development' + +h2. What? + +RAD is a framework for programming the Arduino physcial computing platform using Ruby. RAD converts Ruby scripts written using a set of Rails-like conventions and helpers into C source code which can be compiled and run on the Arduino microcontroller. It also provides a set of Rake tasks for automating the compilation and upload process. + +h2. Why? + +While duplicating the functionality of the well-designed Arduino software interface in Ruby may seem like an odd or redundant goal, RAD has further ambitions! Bootstrapping the ability to write microcontroller code in a high level dynamic language like Ruby could greatly ease the creation of all the luxurious development aids the users of such a language have come to expect: developer testing, platform independence, easy metaprogramming, etc. + +h2. Installing + +@$ sudo gem install rad@ + +You'll also need to have the Arduino environment installed, which you can get from "the Arduino website":http://www.arduino.cc/en/Main/Software. RAD currently requires Arduino 0010, but we try to keep it up-to-date with new Arduino releases. + +h2. The Basics + +@$ rad my_sketch@ + +This command will create a new RAD project directory (my_sketch/) inside of the current directory that contains a blank script in which you can write your own RAD code, as well as a full install of the RAD support infrastructure (in vendor/). A sample 'hello world' script in RAD will look like this: + +
+class MySketch < ArduinoSketch
+  output_pin 7, :as => :led
+  def loop
+    blink led, 500
+  end
+end
+
+ +Once your code is written, your relevant local configuration properly setup in @config/hardware.rb@ and @config/software.rb@, and an Arduino with the corresponding circuit (a 220ohm resistor and an LED wired in series between Arduino pin 7 and ground) is connected to your computer via serial, run: + +@$ rake make:upload@ + +This will: + + * generate the correct Arduino C++ code from your sketch + * dynamically prepare a localized version of the default Arduino Makefile + * compile your sketch + * prompt you to hit the reset button on your Arduino (if necessary!) + * upload your compiled binary onto your Arduino + +h2. The Arduino API + +With the exception of the still-experimental Serial interface, most of the Arduino software API should be working correctly at this point. Documentation for the Ruby versions of the methods is forthcoming, but it is mostly what you'd expect: methods with identical names and arguments to their Arduino counterparts with the exception of using 'true' and 'false' for HIGH and LOW. + +h2. RAD Needs You! + +All the many discipline-crossing skills required for a project like RAD make for lots of opportunities to help out: Have you written lots of sketches exploring the obscure depths of the Arduino library? Do you run the Arduino development tool chain on an obscure (i.e., non-OS X) platform? Do you develop for other AVR or PIC microcontrollers? Are you a C/C++ ninja? Or even C/C++ competent? + +There's lots to do. + +If you're looking for a place to dive in and don't know quite where, "email Greg":mailto:greg@grabb.it. If you want to start by taking a log at the code, the trunk repository is @svn://rubyforge.org/var/svn/rad/trunk@ for anonymous access. + +h2. License + +This code is free to use under the terms of the GPL 2.0 license, just like the Arduino software library itself. + +h2. Contact + +Comments, questions, heckles, attacks, praises, and, (most especially) patches and contributions are welcome! Send email to "Greg Borenstein":mailto:greg@grabb.it. diff --git a/website/javascripts/rounded_corners_lite.inc.js b/website/javascripts/rounded_corners_lite.inc.js new file mode 100644 index 0000000..afc3ea3 --- /dev/null +++ b/website/javascripts/rounded_corners_lite.inc.js @@ -0,0 +1,285 @@ + + /**************************************************************** + * * + * curvyCorners * + * ------------ * + * * + * This script generates rounded corners for your divs. * + * * + * Version 1.2.9 * + * Copyright (c) 2006 Cameron Cooke * + * By: Cameron Cooke and Tim Hutchison. * + * * + * * + * Website: http://www.curvycorners.net * + * Email: info@totalinfinity.com * + * Forum: http://www.curvycorners.net/forum/ * + * * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU * + * Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will * + * be useful, but WITHOUT ANY WARRANTY; without even the * + * implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU Lesser General Public * + * License for more details. * + * * + * You should have received a copy of the GNU Lesser * + * General Public License along with this library; * + * Inc., 59 Temple Place, Suite 330, Boston, * + * MA 02111-1307 USA * + * * + ****************************************************************/ + +var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1; var isMoz = document.implementation && document.implementation.createDocument; var isSafari = ((navigator.userAgent.toLowerCase().indexOf('safari')!=-1)&&(navigator.userAgent.toLowerCase().indexOf('mac')!=-1))?true:false; function curvyCorners() +{ if(typeof(arguments[0]) != "object") throw newCurvyError("First parameter of curvyCorners() must be an object."); if(typeof(arguments[1]) != "object" && typeof(arguments[1]) != "string") throw newCurvyError("Second parameter of curvyCorners() must be an object or a class name."); if(typeof(arguments[1]) == "string") +{ var startIndex = 0; var boxCol = getElementsByClass(arguments[1]);} +else +{ var startIndex = 1; var boxCol = arguments;} +var curvyCornersCol = new Array(); if(arguments[0].validTags) +var validElements = arguments[0].validTags; else +var validElements = ["div"]; for(var i = startIndex, j = boxCol.length; i < j; i++) +{ var currentTag = boxCol[i].tagName.toLowerCase(); if(inArray(validElements, currentTag) !== false) +{ curvyCornersCol[curvyCornersCol.length] = new curvyObject(arguments[0], boxCol[i]);} +} +this.objects = curvyCornersCol; this.applyCornersToAll = function() +{ for(var x = 0, k = this.objects.length; x < k; x++) +{ this.objects[x].applyCorners();} +} +} +function curvyObject() +{ this.box = arguments[1]; this.settings = arguments[0]; this.topContainer = null; this.bottomContainer = null; this.masterCorners = new Array(); this.contentDIV = null; var boxHeight = get_style(this.box, "height", "height"); var boxWidth = get_style(this.box, "width", "width"); var borderWidth = get_style(this.box, "borderTopWidth", "border-top-width"); var borderColour = get_style(this.box, "borderTopColor", "border-top-color"); var boxColour = get_style(this.box, "backgroundColor", "background-color"); var backgroundImage = get_style(this.box, "backgroundImage", "background-image"); var boxPosition = get_style(this.box, "position", "position"); var boxPadding = get_style(this.box, "paddingTop", "padding-top"); this.boxHeight = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight)); this.boxWidth = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth)); this.borderWidth = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0)); this.boxColour = format_colour(boxColour); this.boxPadding = parseInt(((boxPadding != "" && boxPadding.indexOf("px") !== -1)? boxPadding.slice(0, boxPadding.indexOf("px")) : 0)); this.borderColour = format_colour(borderColour); this.borderString = this.borderWidth + "px" + " solid " + this.borderColour; this.backgroundImage = ((backgroundImage != "none")? backgroundImage : ""); this.boxContent = this.box.innerHTML; if(boxPosition != "absolute") this.box.style.position = "relative"; this.box.style.padding = "0px"; if(isIE && boxWidth == "auto" && boxHeight == "auto") this.box.style.width = "100%"; if(this.settings.autoPad == true && this.boxPadding > 0) +this.box.innerHTML = ""; this.applyCorners = function() +{ for(var t = 0; t < 2; t++) +{ switch(t) +{ case 0: +if(this.settings.tl || this.settings.tr) +{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0); newMainContainer.style.height = topMaxRadius + "px"; newMainContainer.style.top = 0 - topMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.topContainer = this.box.appendChild(newMainContainer);} +break; case 1: +if(this.settings.bl || this.settings.br) +{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0); newMainContainer.style.height = botMaxRadius + "px"; newMainContainer.style.bottom = 0 - botMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.bottomContainer = this.box.appendChild(newMainContainer);} +break;} +} +if(this.topContainer) this.box.style.borderTopWidth = "0px"; if(this.bottomContainer) this.box.style.borderBottomWidth = "0px"; var corners = ["tr", "tl", "br", "bl"]; for(var i in corners) +{ if(i > -1 < 4) +{ var cc = corners[i]; if(!this.settings[cc]) +{ if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null)) +{ var newCorner = document.createElement("DIV"); newCorner.style.position = "relative"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; if(this.backgroundImage == "") +newCorner.style.backgroundColor = this.boxColour; else +newCorner.style.backgroundImage = this.backgroundImage; switch(cc) +{ case "tl": +newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.left = -this.borderWidth + "px"; break; case "tr": +newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.tl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; newCorner.style.left = this.borderWidth + "px"; break; case "bl": +newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.br.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = -this.borderWidth + "px"; newCorner.style.backgroundPosition = "-" + (this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break; case "br": +newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.bl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = this.borderWidth + "px" +newCorner.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break;} +} +} +else +{ if(this.masterCorners[this.settings[cc].radius]) +{ var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);} +else +{ var newCorner = document.createElement("DIV"); newCorner.style.height = this.settings[cc].radius + "px"; newCorner.style.width = this.settings[cc].radius + "px"; newCorner.style.position = "absolute"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth); for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++) +{ if((intx +1) >= borderRadius) +var y1 = -1; else +var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1); if(borderRadius != j) +{ if((intx) >= borderRadius) +var y2 = -1; else +var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2))); if((intx+1) >= j) +var y3 = -1; else +var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);} +if((intx) >= j) +var y4 = -1; else +var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2))); if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius); if(borderRadius != j) +{ for(var inty = (y1 + 1); inty < y2; inty++) +{ if(this.settings.antiAlias) +{ if(this.backgroundImage != "") +{ var borderFract = (pixelFraction(intx, inty, borderRadius) * 100); if(borderFract < 30) +{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);} +else +{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);} +} +else +{ var pixelcolour = BlendColour(this.boxColour, this.borderColour, pixelFraction(intx, inty, borderRadius)); this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius, cc);} +} +} +if(this.settings.antiAlias) +{ if(y3 >= y2) +{ if (y2 == -1) y2 = 0; this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, 0);} +} +else +{ if(y3 >= y1) +{ this.drawPixel(intx, (y1 + 1), this.borderColour, 100, (y3 - y1), newCorner, 0, 0);} +} +var outsideColour = this.borderColour;} +else +{ var outsideColour = this.boxColour; var y3 = y1;} +if(this.settings.antiAlias) +{ for(var inty = (y3 + 1); inty < y4; inty++) +{ this.drawPixel(intx, inty, outsideColour, (pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);} +} +} +this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);} +if(cc != "br") +{ for(var t = 0, k = newCorner.childNodes.length; t < k; t++) +{ var pixelBar = newCorner.childNodes[t]; var pixelBarTop = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px"))); var pixelBarLeft = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px"))); var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px"))); if(cc == "tl" || cc == "bl"){ pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px";} +if(cc == "tr" || cc == "tl"){ pixelBar.style.top = this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px";} +switch(cc) +{ case "tr": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "tl": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "bl": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth) + "px"; break;} +} +} +} +if(newCorner) +{ switch(cc) +{ case "tl": +if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "tr": +if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "bl": +if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break; case "br": +if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break;} +} +} +} +var radiusDiff = new Array(); radiusDiff["t"] = Math.abs(this.settings.tl.radius - this.settings.tr.radius) +radiusDiff["b"] = Math.abs(this.settings.bl.radius - this.settings.br.radius); for(z in radiusDiff) +{ if(z == "t" || z == "b") +{ if(radiusDiff[z]) +{ var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r"); var newFiller = document.createElement("DIV"); newFiller.style.height = radiusDiff[z] + "px"; newFiller.style.width = this.settings[smallerCornerType].radius+ "px" +newFiller.style.position = "absolute"; newFiller.style.fontSize = "1px"; newFiller.style.overflow = "hidden"; newFiller.style.backgroundColor = this.boxColour; switch(smallerCornerType) +{ case "tl": +newFiller.style.bottom = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.topContainer.appendChild(newFiller); break; case "tr": +newFiller.style.bottom = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.topContainer.appendChild(newFiller); break; case "bl": +newFiller.style.top = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.bottomContainer.appendChild(newFiller); break; case "br": +newFiller.style.top = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.bottomContainer.appendChild(newFiller); break;} +} +var newFillerBar = document.createElement("DIV"); newFillerBar.style.position = "relative"; newFillerBar.style.fontSize = "1px"; newFillerBar.style.overflow = "hidden"; newFillerBar.style.backgroundColor = this.boxColour; newFillerBar.style.backgroundImage = this.backgroundImage; switch(z) +{ case "t": +if(this.topContainer) +{ if(this.settings.tl.radius && this.settings.tr.radius) +{ newFillerBar.style.height = topMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.tl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.tr.radius - this.borderWidth + "px"; newFillerBar.style.borderTop = this.borderString; if(this.backgroundImage != "") +newFillerBar.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; this.topContainer.appendChild(newFillerBar);} +this.box.style.backgroundPosition = "0px -" + (topMaxRadius - this.borderWidth) + "px";} +break; case "b": +if(this.bottomContainer) +{ if(this.settings.bl.radius && this.settings.br.radius) +{ newFillerBar.style.height = botMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.bl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.br.radius - this.borderWidth + "px"; newFillerBar.style.borderBottom = this.borderString; if(this.backgroundImage != "") +newFillerBar.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (topMaxRadius + this.borderWidth)) + "px"; this.bottomContainer.appendChild(newFillerBar);} +} +break;} +} +} +if(this.settings.autoPad == true && this.boxPadding > 0) +{ var contentContainer = document.createElement("DIV"); contentContainer.style.position = "relative"; contentContainer.innerHTML = this.boxContent; contentContainer.className = "autoPadDiv"; var topPadding = Math.abs(topMaxRadius - this.boxPadding); var botPadding = Math.abs(botMaxRadius - this.boxPadding); if(topMaxRadius < this.boxPadding) +contentContainer.style.paddingTop = topPadding + "px"; if(botMaxRadius < this.boxPadding) +contentContainer.style.paddingBottom = botMaxRadius + "px"; contentContainer.style.paddingLeft = this.boxPadding + "px"; contentContainer.style.paddingRight = this.boxPadding + "px"; this.contentDIV = this.box.appendChild(contentContainer);} +} +this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius) +{ var pixel = document.createElement("DIV"); pixel.style.height = height + "px"; pixel.style.width = "1px"; pixel.style.position = "absolute"; pixel.style.fontSize = "1px"; pixel.style.overflow = "hidden"; var topMaxRadius = Math.max(this.settings["tr"].radius, this.settings["tl"].radius); if(image == -1 && this.backgroundImage != "") +{ pixel.style.backgroundImage = this.backgroundImage; pixel.style.backgroundPosition = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + topMaxRadius + inty) -this.borderWidth) + "px";} +else +{ pixel.style.backgroundColor = colour;} +if (transAmount != 100) +setOpacity(pixel, transAmount); pixel.style.top = inty + "px"; pixel.style.left = intx + "px"; newCorner.appendChild(pixel);} +} +function insertAfter(parent, node, referenceNode) +{ parent.insertBefore(node, referenceNode.nextSibling);} +function BlendColour(Col1, Col2, Col1Fraction) +{ var red1 = parseInt(Col1.substr(1,2),16); var green1 = parseInt(Col1.substr(3,2),16); var blue1 = parseInt(Col1.substr(5,2),16); var red2 = parseInt(Col2.substr(1,2),16); var green2 = parseInt(Col2.substr(3,2),16); var blue2 = parseInt(Col2.substr(5,2),16); if(Col1Fraction > 1 || Col1Fraction < 0) Col1Fraction = 1; var endRed = Math.round((red1 * Col1Fraction) + (red2 * (1 - Col1Fraction))); if(endRed > 255) endRed = 255; if(endRed < 0) endRed = 0; var endGreen = Math.round((green1 * Col1Fraction) + (green2 * (1 - Col1Fraction))); if(endGreen > 255) endGreen = 255; if(endGreen < 0) endGreen = 0; var endBlue = Math.round((blue1 * Col1Fraction) + (blue2 * (1 - Col1Fraction))); if(endBlue > 255) endBlue = 255; if(endBlue < 0) endBlue = 0; return "#" + IntToHex(endRed)+ IntToHex(endGreen)+ IntToHex(endBlue);} +function IntToHex(strNum) +{ base = strNum / 16; rem = strNum % 16; base = base - (rem / 16); baseS = MakeHex(base); remS = MakeHex(rem); return baseS + '' + remS;} +function MakeHex(x) +{ if((x >= 0) && (x <= 9)) +{ return x;} +else +{ switch(x) +{ case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F";} +} +} +function pixelFraction(x, y, r) +{ var pixelfraction = 0; var xvalues = new Array(1); var yvalues = new Array(1); var point = 0; var whatsides = ""; var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2))); if ((intersect >= y) && (intersect < (y+1))) +{ whatsides = "Left"; xvalues[point] = 0; yvalues[point] = intersect - y; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2))); if ((intersect >= x) && (intersect < (x+1))) +{ whatsides = whatsides + "Top"; xvalues[point] = intersect - x; yvalues[point] = 1; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2))); if ((intersect >= y) && (intersect < (y+1))) +{ whatsides = whatsides + "Right"; xvalues[point] = 1; yvalues[point] = intersect - y; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2))); if ((intersect >= x) && (intersect < (x+1))) +{ whatsides = whatsides + "Bottom"; xvalues[point] = intersect - x; yvalues[point] = 0;} +switch (whatsides) +{ case "LeftRight": +pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2); break; case "TopRight": +pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2); break; case "TopBottom": +pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2); break; case "LeftBottom": +pixelfraction = (yvalues[0]*xvalues[1])/2; break; default: +pixelfraction = 1;} +return pixelfraction;} +function rgb2Hex(rgbColour) +{ try{ var rgbArray = rgb2Array(rgbColour); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); var hexColour = "#" + IntToHex(red) + IntToHex(green) + IntToHex(blue);} +catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");} +return hexColour;} +function rgb2Array(rgbColour) +{ var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")")); var rgbArray = rgbValues.split(", "); return rgbArray;} +function setOpacity(obj, opacity) +{ opacity = (opacity == 100)?99.999:opacity; if(isSafari && obj.tagName != "IFRAME") +{ var rgbArray = rgb2Array(obj.style.backgroundColor); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); obj.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity/100 + ")";} +else if(typeof(obj.style.opacity) != "undefined") +{ obj.style.opacity = opacity/100;} +else if(typeof(obj.style.MozOpacity) != "undefined") +{ obj.style.MozOpacity = opacity/100;} +else if(typeof(obj.style.filter) != "undefined") +{ obj.style.filter = "alpha(opacity:" + opacity + ")";} +else if(typeof(obj.style.KHTMLOpacity) != "undefined") +{ obj.style.KHTMLOpacity = opacity/100;} +} +function inArray(array, value) +{ for(var i = 0; i < array.length; i++){ if (array[i] === value) return i;} +return false;} +function inArrayKey(array, value) +{ for(key in array){ if(key === value) return true;} +return false;} +function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true;} +else if (elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r;} +else { elm['on' + evType] = fn;} +} +function removeEvent(obj, evType, fn, useCapture){ if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true;} else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r;} else { alert("Handler could not be removed");} +} +function format_colour(colour) +{ var returnColour = "#ffffff"; if(colour != "" && colour != "transparent") +{ if(colour.substr(0, 3) == "rgb") +{ returnColour = rgb2Hex(colour);} +else if(colour.length == 4) +{ returnColour = "#" + colour.substring(1, 2) + colour.substring(1, 2) + colour.substring(2, 3) + colour.substring(2, 3) + colour.substring(3, 4) + colour.substring(3, 4);} +else +{ returnColour = colour;} +} +return returnColour;} +function get_style(obj, property, propertyNS) +{ try +{ if(obj.currentStyle) +{ var returnVal = eval("obj.currentStyle." + property);} +else +{ if(isSafari && obj.style.display == "none") +{ obj.style.display = ""; var wasHidden = true;} +var returnVal = document.defaultView.getComputedStyle(obj, '').getPropertyValue(propertyNS); if(isSafari && wasHidden) +{ obj.style.display = "none";} +} +} +catch(e) +{ } +return returnVal;} +function getElementsByClass(searchClass, node, tag) +{ var classElements = new Array(); if(node == null) +node = document; if(tag == null) +tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)"); for (i = 0, j = 0; i < elsLen; i++) +{ if(pattern.test(els[i].className)) +{ classElements[j] = els[i]; j++;} +} +return classElements;} +function newCurvyError(errorMessage) +{ return new Error("curvyCorners Error:\n" + errorMessage) +} diff --git a/website/stylesheets/code.css b/website/stylesheets/code.css new file mode 100644 index 0000000..cd019cb --- /dev/null +++ b/website/stylesheets/code.css @@ -0,0 +1,17 @@ +.ruby .normal {} +.ruby .comment { color: #888; font-style: italic; } +.ruby .keyword { color: #A00; font-weight: bold; } +.ruby .method { color: #077; } +.ruby .class { color: #074; } +.ruby .module { color: #050; } +.ruby .punct { color: #447; font-weight: bold; } +.ruby .symbol { color: #099; } +.ruby .string { color: #944; } +.ruby .char { color: #F07; } +.ruby .ident { color: #004; } +.ruby .constant { color: #07F; } +.ruby .regex { color: #B66; } +.ruby .number { color: #D55; } +.ruby .attribute { color: #377; } +.ruby .global { color: #3B7; } +.ruby .expr { color: #227; }) \ No newline at end of file diff --git a/website/stylesheets/screen.css b/website/stylesheets/screen.css new file mode 100644 index 0000000..b7317cd --- /dev/null +++ b/website/stylesheets/screen.css @@ -0,0 +1,169 @@ +body { + background-color: #E1D1F1; + font-family: "Georgia", sans-serif; + font-size: 16px; + line-height: 1.6em; + padding: 1.6em 0 0 0; + color: #333; +} +h1, h2, h3, h4, h5, h6 { + color: #444; +} +h1 { + font-family: sans-serif; + font-weight: normal; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; + margin: 5px; +} +li { + padding: 0; + margin: 0; + list-style-type: square; +} +a { + color: #5E5AFF; + background-color: #DAC; + font-weight: normal; + text-decoration: underline; +} +blockquote { + font-size: 90%; + font-style: italic; + border-left: 1px solid #111; + padding-left: 1em; +} + +#buy-arduino { + float:left; + margin-right: 10px; + border: 8px solid #000; + background-color: #fff; + padding: 5px; + text-align:center; +} + +#buy-arduino h4{ + font-size: 12px; + margin: 0; +} + +#buy-arduino a { + background-color: #fff; +} + +#buy-arduino img{ + width: 190px; + border: none; +} + +.caps { + font-size: 80%; +} + +#main { + width: 45em; + padding: 0; + margin: 0 auto; +} +.coda { + text-align: right; + color: #77f; + font-size: smaller; +} + +table { + font-size: 90%; + line-height: 1.4em; + color: #ff8; + background-color: #111; + padding: 2px 10px 2px 10px; + border-style: dashed; +} + +th { + color: #fff; +} + +td { + padding: 2px 10px 2px 10px; +} + +.success { + color: #0CC52B; +} + +.failed { + color: #E90A1B; +} + +.unknown { + color: #995000; +} +pre, code { + font-family: monospace; + font-size: 90%; + line-height: 1.4em; + color: #ff8; + background-color: #111; + padding: 2px 10px 2px 10px; +} +.comment { color: #aaa; font-style: italic; } +.keyword { color: #eff; font-weight: bold; } +.punct { color: #eee; font-weight: bold; } +.symbol { color: #0bb; } +.string { color: #6b4; } +.ident { color: #ff8; } +.constant { color: #66f; } +.regex { color: #ec6; } +.number { color: #F99; } +.expr { color: #227; } + +#metadata { + float: left; + margin-right: 20px; +} + +#version { + width:210px; + text-align: right; + font-family: sans-serif; + font-weight: normal; + background-color: #B3ABFF; + color: #141331; + padding: 15px 20px 10px 20px; + margin: 0 auto; + margin-top: 15px; + border: 3px solid #141331; + margin-bottom: 20px; + +} + +#version .numbers { + display: block; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; + margin-bottom: 15px; +} + +#version p { + text-decoration: none; + color: #141331; + background-color: #B3ABFF; + margin: 0; + padding: 0; +} + +#version a { + text-decoration: none; + color: #141331; + background-color: #B3ABFF; +} + +.clickable { + cursor: pointer; + cursor: hand; +} + diff --git a/website/template.rhtml b/website/template.rhtml new file mode 100644 index 0000000..4abf70d --- /dev/null +++ b/website/template.rhtml @@ -0,0 +1,48 @@ + + + + + + + <%= title %> + + + + + + +
+ +

<%= title %>

+
+

Get Version

+ <%= version %> +
+ <%= body %> +

+ Dr Nic, <%= modified.pretty %>
+ Theme extended from Paul Battley +

+
+ + + + +