From 2051882809fda3890fbdb967e8694c6dc705a9bb Mon Sep 17 00:00:00 2001 From: sandal Date: Fri, 30 Jun 2006 14:32:36 +0000 Subject: [PATCH] initial import git-svn-id: http://stonecode.svnrepository.com/svn/ruport/trunk/ruport@1 bb2e8eb0-7117-0410-aac4-c024b40ed5f7 --- ACKNOWLEDGEMENTS | 36 + AUTHORS | 21 + CHANGELOG | 371 +++++ COPYING | 340 +++++ LICENSE | 13 + README | 178 +++ Rakefile | 71 + TODO | 23 + doc/recipes/data_acquisition.textile | 547 +++++++ doc/recipes/data_set_manipulation.textile | 226 +++ doc/recipes/email.textile | 19 + doc/recipes/extending_ruport.textile | 20 + doc/recipes/formatting_system.textile | 15 + doc/recipes/resources.textile | 60 + doc/ruport-milestone-0.png | Bin 0 -> 47797 bytes doc/ruport_recipes.textile | 58 + examples/fieldless_table.rb | 10 + examples/line_plotter.rb | 44 + examples/long.txt | 24 + examples/new_plugin.rb | 21 + examples/simple_mail.rb | 15 + lib/ruport.rb | 65 + lib/ruport/config.rb | 127 ++ lib/ruport/data_row.rb | 181 +++ lib/ruport/data_set.rb | 299 ++++ lib/ruport/format.rb | 151 ++ lib/ruport/format/document.rb | 78 + lib/ruport/format/engine.rb | 142 ++ lib/ruport/format/open_node.rb | 38 + lib/ruport/format/plugin.rb | 176 +++ lib/ruport/mailer.rb | 50 + lib/ruport/query.rb | 207 +++ lib/ruport/query/sql_split.rb | 33 + lib/ruport/rails.rb | 2 + lib/ruport/rails/reportable.rb | 32 + lib/ruport/report.rb | 88 ++ lib/ruport/system_extensions.rb | 125 ++ lib/uport.rb | 1 + setup.rb | 1585 +++++++++++++++++++++ test/samples/addressbook.csv | 6 + test/samples/data.csv | 3 + test/samples/document.xml | 22 + test/samples/ruport_test.sql | 8 + test/samples/stonecodeblog.sql | 279 ++++ test/samples/test.sql | 2 + test/samples/test.yaml | 3 + test/tc_config.rb | 89 ++ test/tc_data_row.rb | 124 ++ test/tc_data_set.rb | 367 +++++ test/tc_database.rb | 25 + test/tc_document.rb | 42 + test/tc_element.rb | 18 + test/tc_format.rb | 37 + test/tc_format_engine.rb | 108 ++ test/tc_page.rb | 42 + test/tc_plugin.rb | 184 +++ test/tc_query.rb | 69 + test/tc_report.rb | 31 + test/tc_ruport.rb | 59 + test/tc_section.rb | 45 + test/tc_sql_split.rb | 18 + test/ts_all.rb | 9 + test/ts_format.rb | 7 + util/release/raa.rb | 13 + 64 files changed, 7102 insertions(+) create mode 100644 ACKNOWLEDGEMENTS create mode 100644 AUTHORS create mode 100644 CHANGELOG create mode 100644 COPYING create mode 100644 LICENSE create mode 100644 README create mode 100644 Rakefile create mode 100644 TODO create mode 100644 doc/recipes/data_acquisition.textile create mode 100644 doc/recipes/data_set_manipulation.textile create mode 100644 doc/recipes/email.textile create mode 100644 doc/recipes/extending_ruport.textile create mode 100644 doc/recipes/formatting_system.textile create mode 100644 doc/recipes/resources.textile create mode 100644 doc/ruport-milestone-0.png create mode 100644 doc/ruport_recipes.textile create mode 100644 examples/fieldless_table.rb create mode 100644 examples/line_plotter.rb create mode 100644 examples/long.txt create mode 100644 examples/new_plugin.rb create mode 100644 examples/simple_mail.rb create mode 100644 lib/ruport.rb create mode 100644 lib/ruport/config.rb create mode 100644 lib/ruport/data_row.rb create mode 100644 lib/ruport/data_set.rb create mode 100644 lib/ruport/format.rb create mode 100644 lib/ruport/format/document.rb create mode 100644 lib/ruport/format/engine.rb create mode 100644 lib/ruport/format/open_node.rb create mode 100644 lib/ruport/format/plugin.rb create mode 100644 lib/ruport/mailer.rb create mode 100644 lib/ruport/query.rb create mode 100644 lib/ruport/query/sql_split.rb create mode 100644 lib/ruport/rails.rb create mode 100644 lib/ruport/rails/reportable.rb create mode 100644 lib/ruport/report.rb create mode 100644 lib/ruport/system_extensions.rb create mode 100644 lib/uport.rb create mode 100644 setup.rb create mode 100644 test/samples/addressbook.csv create mode 100644 test/samples/data.csv create mode 100644 test/samples/document.xml create mode 100644 test/samples/ruport_test.sql create mode 100644 test/samples/stonecodeblog.sql create mode 100644 test/samples/test.sql create mode 100644 test/samples/test.yaml create mode 100644 test/tc_config.rb create mode 100644 test/tc_data_row.rb create mode 100644 test/tc_data_set.rb create mode 100644 test/tc_database.rb create mode 100644 test/tc_document.rb create mode 100644 test/tc_element.rb create mode 100644 test/tc_format.rb create mode 100644 test/tc_format_engine.rb create mode 100644 test/tc_page.rb create mode 100644 test/tc_plugin.rb create mode 100644 test/tc_query.rb create mode 100644 test/tc_report.rb create mode 100644 test/tc_ruport.rb create mode 100644 test/tc_section.rb create mode 100644 test/tc_sql_split.rb create mode 100644 test/ts_all.rb create mode 100644 test/ts_format.rb create mode 100644 util/release/raa.rb diff --git a/ACKNOWLEDGEMENTS b/ACKNOWLEDGEMENTS new file mode 100644 index 00000000..c562b132 --- /dev/null +++ b/ACKNOWLEDGEMENTS @@ -0,0 +1,36 @@ +Sometimes you just gotta pay some mad propz to the peoples. + +- Daniel Berger for releasing gruf to the community + +- James Edward Gray II for letting me take query.rb and mash it into something + that eventually became Ruport 0.1.0 + +- Austin Ziegler for suggesting the Ruby License, which hopefully keeps everyone + happy. + +- All the people at RubyConf 2005, new_haven.rb, and NYC.rb that made + suggestions and let me know what kind of problems they were dealing with. + +- The people on the Ruport mailing list who will happily run unit tests and + check out little things for me when I need it. + +- Francis Hwang for overall design suggestions and the contribution of SqlSplit. + Additionally, his efforts on DBI to bring it back to life. (Along with the + rest of the new DBI team) + +- Mathijs Mohlmann for pointing out that the DataRow constructor sucks and + offering some patches for changes to Ruport. + +- Gregory Gibson for pretending like I'm a worthwhile business investment, and + providing funding to let me work on Ruport, instead of like... working in a + coffee shop or something. + +- 'A Monkey' for catching a bug before I even commited it to SVN. + +- Simon Claret for neato PDF table support + +- Dudley Flanders for DataSet psuedo keyword support + +- RubyTalk, #ruby-lang and the Ruby community in general. + Each and every one of you is my homie. + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..dfd5e785 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,21 @@ +Developers: +--------------------------------------------------- + +{Gregory Brown}[mailto:gregory.t.brown@gmail.com] +{Dudley Flanders}[mailto:dudley@misnomer.us] + +Contributors / People we've (legally) stolen from: +--------------------------------------------------- + +James Edward Gray II: +- Original inspiration via query.rb +- system_extensions.rb + +Francis Hwang: +- SQLSplit + +Simon Claret: +- PDF table support + +Dinko Mehinovic: +- util/release/raa.rb diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 00000000..14a2f0fa --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,371 @@ +The current version of Ruby Reports is 0.4.13 + +changes since 0.4.11 + +- I apparently never knew how to use inject. + Injects are all functional now. + +- Fixed a bug in DataSet that made fields not get duped properly + +changes since 0.4.9 + +- DataSet#column_names and DataRow#column_names have been added as alias to + fields. + +- Plugins are now safely copied when used via DataSet#as or the generated + Format::simple_interface(Format.table,Format.document,etc) + +- Dropped insert_row / insert_column from Format::Engine::Table. + These are no longer needed. + +- Reworked format engine unit tests to decouple from specific plugins. + +- vendored SystemExtensions from HighLine to get terminal_width / + terminal_height functions + +- DataSet#add_columns and DataSet#add_columns! has been added + +- Added pre and post hooks for Plugins. Individual plugins choose if and + how to implement them. + +- Parser is gone + +- Format::Builder is gone + +- fixed a bug in rails support. :columns now works properly. + +- fieldnames can now be disabled in CSV loading + +- DataSet.load now loads empty cells as nil instead "" by default + +- fixed HTML table output in HTMLPlugin + +- Select / Remove columns now accepts ordinal indexes + +changes since 0.4.5: + +- Added acts_as_reportable for ActiveRecord / rails. whoo + +- Mailer now can handle attachments and html emails. + API Breakage. Needs mailfactory unless you use the hooks for other + mailers. + +- Added a rewrite_column action to the tabular engine + +- Added Ruport.configure shortcut interface + +- went back to the old style of output for DataSet.to_s + Modified TextPlugin to accomplish this + +- Crackrock feature: Engine forwards enumerable methods to it's data. + +- DataSets and DataRows can now be created without specifying field names. + +- Added uport.rb . I needed to steal from RubyGems :) + +- Fixed some requires + +- Temporarily removed mail support from Ruport::Report + +- Added Ruport::Mailer#deliver simple mail interface + +- Ruport::Report::Mailer is now Ruport::Mailer + +changes since 0.4.4: + +- DataRow constructor now matches DataSet style. + +- DataSet picked up a ton of array-like functionality + +- DataSet#load now optionally accepts a block that acts something + like inject. + +- Added a DataSet#select_columns!() method + +- What the heck was Format#filter_ruby? It's gone now + +- DataSet#as() now hooked to the new formatting system. + note API breakage in block forms + +- DataSet set operations (DF) + +- Much smarter DataSet#<< (Dudley Flanders) + +- Ruport now has pseudo keyword support in DataSets. + (Thanks to Dudley Flanders!) + +changes since 0.4.2: + +- Fixed a packaging bug. + +changes since 0.4.0: + +- Brand new formatting system. + See: http://ruport.infogami.com/Formatting_System_HOWTO + +- DataSet#<< now returns self, e.g. + some_data << [1,2,3] << [4,5,6] + +- Removed fascist opt_require feature and Ruport::Base + +- Fixed bug in DataRow constructor which destroyed arrays. + +- Examples now included in gems + +changes since 0.3.8: + +- added DataRow#to_h + +- Ruport::Format.register_filter now passes the content it will modify via a + block. e.g. + + Ruport::Format.register_filter :reverser { |content| content.reverse } + +- Modified the license terms of Ruport so that it uses specifically the GPLv2. + Further versions of Ruport may not be distributed under later versions of the + GPL without explicit permission. + +- DataSet#[]= now passes rvals to DataRow constructor with fields from the + DataSet it is called on. + +- Optional dependencies should now be forceably protected by opt_require. + +- Fixed a problem with Ruport.complain that caused it to force you to use a + logger to make it work. + +- DataRow#eql? is fixed and now has a test covering it. + +- DataRow/DataSet can now be dup'ed / cloned + (But may still have some dragons) + +- DataRow#+ now returns DataRows instead of arrays + +- Format::Builder rendering methods can now be registered through + Format::Builder.register_rendering_method() + +- DataRow["somekeythatdoesntexist"] no longer throws an error + (now returns nil) + +- Now some_data_row[:foo] and some_data_row["foo"] are equivalent + +- Fixed Query so it throws appropriate errors. No more magic nil. (I hope) + +- Ruport::Config.default_source now defaults to nil + +- Ruport::Config now has improved error detection. + +- additional Test coverage, documentation and logging. + +- DataSet#sigma (a.k.a DataSet#sum) added for summations. + +- Format::Builder#render_pdf renamed to Format:Builder#render_pdf_document + +- Format::Builder#render_pdf_table added. (Thanks Simon Claret!) + +- Fixed bug in remove_columns + +key features of Ruport 0.3.8: + +- New configuration system + +- New query model + +- New formatting system + +- Total API redesign + +- Lots more API documentation + +- A parser adapted from Parse::Input + +- ruport executable was deprecated + +[ Code was redesigned completely after 0.2.9 ] + +changes since Ruport 0.2.9: + +- Report::SQL dropped. + +- Fixed a bug in query that made ODBC driver not work at ALL! + (AFAIK, this bug was ONLY in Ruport 0.2.9) + +- removed render() from the engine and implemented DataSet#render_as() + example: + @report = render(data) do |builder| builder.format = :some_format end + is now: @report = data.render_as(:some_format) + which takes an optional block that works as before. + +- removed method DataSet#select_field() because it was the same as + DataSet#select_fields() and of limited utility. + +- added method DataSet#remove_fields and DataSet#remove_fields! + to make data manipulation easier. + +- added DataSet#empty? + +- added DataSet#clone which will actually deep copy a DataSet. + +- cleaned up incredibly annoying DataSet constructor so you can now pass + field names and data to new() + +- Report::DataSet now documented + +- added hacks.rb which will include a random collection of potentially + useful functions. (Already cool for making Ruport easy to use with irb). + +- added Document class and unit tests. Needs to be completed. + +- added Report::DataSet.clone for making deep copies of DataSets. + +- added Element, Section, Page, and Document formatting classes. + +- restructured unit tests, adding ts_format and ts_report + and moving all non-testcase data into test/samples + +- restuctured library, adding ruport/format.rb and ruport/report.rb + made format and report into classes, with nested classes within. + +- Moved the Report::Engine class into Report + +- reorginized DataSet#<< to be more efficient. Thanks Francis! + +- added OpenNode. Which is a little scary. + +- Francis Hwang has added SqlSplit, which has been tied into query. + Now multiple statement SQL dumps can be processed by ruport. + +- DataSet can now safely execute commands that return no rows. + (This was a bug in RubyDBI which ruport now has a workaround for) + +- require "ruportlib" becomes require "ruport" + + +changes since Ruport 0.2.5: + +- Added a new examples package with a 666 line walkthrough and more enjoyable + demonstratons + +- Added support for multiple DSN's via add_dsn and select_dsn + +- Added Tagging support for DataSets. + +- Added DataSet#select_field and DataSet#select_fields to simplify grabbing + data by column + +- Report::DataSet now implements to_s, for pretty printing goodness. + +- render_text now implemented in Format::Builder + +- Format::Builder now accepts headers and footers. + +- Format::Builder now can generate complete html via output_type + +- Ruport now uses FasterCSV, which is now a dependency. Yay! Dependencies! + (But it IS 9x faster than the CSV lib that ships with Ruby) + +- Added tests to the gem spec... so you can use -t now. + +- DataRow#+ implemented. + +- Format::Builder#range implemented. (Can now format partial DataSets) + +- Fixed a bug in the unit tests that required Ruport to be installed. + Now you can test BEFORE you install. (Sorry about that one...) + +- Commands that return no rows no longer crash the application + (But writing to the database is still not fun nor easy) + +- templates/ folder now more aptly named scripts/ + +- Report::Engine#query now returns a DataSet so chaining is possible + +changes since Ruport 0.2.2: + +- Report::DataSet and Report::DataRow are now enumerable + +- DataSet#eql? fixed + +- Format::Builder has been added to handle formatting + (Supports CSV / HTML currently) + +- Format::Builder is easily extendable via send("render_#{@type}") + +- DataSet#to_html convenience method added + +- DataSet#fields can now be arbitrary objects (does not need to be Strings) + +- Dropped DataRow#middle? because it was useless. (And poorly named) + +- FakeMailer added for testing goodness. + +- Dropped select() and execute() in favor of query() + +changes since Ruport 0.2.0: + +- Report becomes Engine +- MockDB becomes FakeDB +- MockReport becomes MockEngine + +- Modules are added so now, + + Ruport::Report::Engine, + Ruport::Report::DataSet is the full name of these classes. + +- 'require ruportlib' runs include Ruport, + so you can type Report::Engine.new instead of Ruport::Report::Engine.new + +- The assert that was supposed to be an assert_equal that + David Black caught was fixed. + +- Examples are updated and now on the RubyForge FRS in many formats. + (With .DS_Store and other garbage files removed! ;) ) + +- Formatting of code was cleaned up and improved. + +- Generates a test.rb that does not use deprecated functions now. + +changes since Ruport 0.1.0: + +CLEANUP / ORGANIZATION: + +- Parseinput dependency removed for now + +- setup.rb support removed + +- gem install will now only install a single executable + +- dropped undocumented YAML override feature + +- Query class now called Report + +- Project split into two parts lib/ruport/db and lib/ruport/format + +- dropped questionable ruport_query manager function + +- finally turned on expandtab and tabstop=2 for Ruby style ;) + +FEATURES: + +- require 'ruportlib' lets ruport be used as a library + +- MockDB and MockReport added to simplify testing + +- DataRow and DataSet added to provide munging with ease + (to_csv, DataSet.load("some.csv"), etc + +- DataSet.load() can restore DataSet's that were YAML.dump()'ed + +- load_file now supports absolute and relative paths + +- added many automation features to templates + +- added @pre and @post to Report which accept lambdas to execute + before and after generate_report() + +- logger added. + +- some friendly error messages added + +- new method query() will eventually replace select() / execute() + +- {examples}[http://ruport.rubyforge.org/] available for download diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..d60c31a9 --- /dev/null +++ b/COPYING @@ -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. + + + Copyright (C) + + 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. + + , 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. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..9bcd3f27 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ += License Terms + +Distributed by Gregory Brown under the user's choice* of the +{GPL version 2}[http://www.gnu.org/copyleft/gpl.html] (see COPYING for details) +or the {Ruby software license}[http://www.ruby-lang.org/en/LICENSE.txt]. + +Please email Greg[mailto:gregory.t.brown_AT_gmail.com] with any questions. + +*Note: This license refers specifically to GPLv2. +Distributing under other versions of the GPL require explicit permission from +Gregory Brown. Though we will most likely adopt the GPLv3 when the final draft +is published, we want to be able to make that decision for ourselves. + diff --git a/README b/README new file mode 100644 index 00000000..5640b458 --- /dev/null +++ b/README @@ -0,0 +1,178 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------- +# +# WARNING: THIS DOCUMENT IS FREQUENTLY OUT OF DATE. +# +# The most up to date information can be found at: +# http://reporting.stonecode.org +# +# Contents: +# +# - What Ruport Is. +# - Installation +# - Caveats. +# - Resources +# - Background and Summary +# +# - What Ruport Is. +# +# Ruby Reports is a software library that aims to make the task of reporting +# less tedious and painful. It provides tools for data acquisition, database +# interaction, formatting, and parsing/munging. Designed to be extensible, +# it is possible to use Ruport for quick specific tasks as well as to build +# robust reporting applications. +# +# - Installation +# +# Optional Dependencies: +# +# Ruport has a number of dependencies: +# +# Ruby/DBI and appropriate dbds: Makes Query useable +# (must be installed manually) +# +# FasterCSV: Enables fast CSV parsing +# (available via rubygems) +# +# RedCloth: Enables textile/markdown filtering +# (available via rubygems) +# +# PDF::Writer: Enables printable documents via render_pdf (Experimental) +# (available via rubygems) +# +# Note that by installing any of the dependencies, either via gems or manually, +# their functionality will automatically be enabled. +# +# To install ruport via rubygems with all it's dependencies (except DBI): +# +# sudo gem install ruport +# +# To install ruport manually via setup.rb: +# +# sudo ruby setup.rb +# +# To not install ruport at all: +# +# ruby -Ipath/to/ruport/lib my_script.rb +# +# Check to see if it installed properly: +# +# ruby -rubygems -e "require 'ruport'; puts Ruport::VERSION" +# (omit the -rubygems flag if installed manually) +# +# If you get an error, please consult the mailing list. +# +# - Caveats +# +# Ruport is alpha software. It's not completely tested and the API is +# changing rapidly from version to version. Test suites are becoming +# increasingly robust, but have not identified all possible edge cases. If +# Ruport goes wild on you, it's because it hasn't been tamed yet. +# +# The functionality is also not complete yet. There is a lot left to be added +# and there is a lot to think about. If you find yourself wondering why +# feature foo is in Ruport, chances are it just hasn't been written yet. +# +# Documentation so far is something that is a struggle to keep up with. As of +# this release, there is at least partial documentation for the API. This +# will continue to get better as time goes on. +# +# Platform independence is a priority, but I don't absolutely always have +# access to every OS / DBMS combination, so if something breaks on your +# system, please feel free to yell loud at the mailing list. +# +# That having been said, I do use ruport in my daily work. That means that it +# will probably have at least something you will find useful. Or so I hope. +# +# - Resources +# +# The best way to get help and make suggestions is the Ruport mailing list. +# This software is on the move, so the list is the most reliable way of getting +# up to date information. +# +# - You can sign up and/or view the archives here: +# http://lists.stonecode.org/listinfo.cgi/ruport-stonecode.org +# +# Please do not hesitate to use this list! I am happily accepting patches and +# documentation, as well as encouraging design discussions and also am quite +# curious about what people use or want to use ruport for. +# +# I will announce Ruport releases on RubyTalk, on the Ruport mailing list, on +# Freshmeat, RAA, and the new_haven.rb mailing list. If you would like to +# keep an eye out for releases, please monitor one of those places. +# +# - You may download Ruport's source from the project page: +# http://rubyforge.org/projects/ruport +# +# - The latest stable API documentation is available at: +# http://ruport.rubyforge.org/docs +# +# There also will be some tutorials on stonecode.org +# +# If you are interested in developing Ruport, please *never* study code in +# official releases. As this software is in it's early stages, it's essential +# to keep an eye on the subversion repository. If you let me know you are +# interested in working on something, I will let you know if I'm actively +# working on that section. +# +# - Grabbing the code from the svn trunk is simple: +# +# svn co svn://rubyforge.org//var/svn/ruport/trunk/ +# +# Those who would like to become regular contributors will be given write +# access. Also, anyone interested in the internal design and project +# management aspects of Ruport can request to be added to our basecamp +# account. This is primarily intended for people who are working on the +# project actively, though. +# +# - Background / Summary +# +# Ruport aims to help you fetch data from various sources, perform +# manipulations on them as needed, and then output them in a variety +# of formats easily. The powerful ERb templating engine is integrated +# to let you embed Ruport code into your formatted data. Also, Ruport +# provides a high level interface to databases, to make getting +# your data easy. +# +# The standard datastructure for Ruport is the DataSet. It is an enumerable +# ordered list which consists of DataRow objects that can be accessed +# by field names or ordinal position. DataRows can be arbitrarily tagged, +# allowing for easy retrieval of the same rows for many different purposes. +# +# The rest of the code is organized into three main models, Report, Query, +# and Format. Each is meant to be a high level interface to Ruport. +# The inner classes can be used where a decent level of customization +# is needed. +# +# Report is in some sense the 'controller' of your application, and provides +# methods to help you write a Reporting application +# +# Format provides support for building filters and specialized formatting +# systems. +# +# Query currently provides a high level interface to DBI. Soon it will support +# a mixin wrapper called Fetchable which will enable you to wrap whatever data +# source you choose. If you would like to query a database or load a sql dump, +# this class can help you do that. It can generate DataSets on the fly, or feed +# you DBI:Rows, depending on what you need. +# +# There is also a Config class which allows you to set things such as data +# sources, mailer information, and logging. Ruport#complain provides a robust +# way to handle error logging and warnings. +# +# Finally, Ruport provides a powerful yet oh-so-scary parsing tool. It is +# essentially James Edward Gray II's Parse::Input library within the Ruport +# library. (And will soon be integrated nicely too) +# +# Read the source of ruport/parser.rb if you have some gnarly data you need to +# munge. +# +# Finally, Please consult the API documentation and/or source code for more +# information. (http://ruport.rubyforge.org/docs). Not all classes have been +# documented but the ones that have may be easier to understand when their docs +# have been read. Also, feel free to contribute documentation. +# +# If you have any questions or concerns, hop on the mailing list and fire away! +# +# Thanks for downloading my software and I hope you enjoy it! +# -Greg diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..85df16ea --- /dev/null +++ b/Rakefile @@ -0,0 +1,71 @@ +require "rake/rdoctask" +require "rake/testtask" +require "rake/gempackagetask" + +begin + require "rubygems" +rescue LoadError + nil +end + +#Set to true to disable dependency resolution +LEAN=false + +task :default => [:test] + +Rake::TestTask.new do |test| + test.libs << "test" + test.test_files = [ "test/ts_all.rb" ] + test.verbose = true +end + +spec = Gem::Specification.new do |spec| + spec.name = LEAN ? "lean-ruport" : "ruport" + spec.version = "0.4.13" + spec.platform = Gem::Platform::RUBY + spec.summary = "A generalized Ruby report generation and templating engine." + spec.files = Dir.glob("{examples,lib,test}/**/**/*") + + ["Rakefile", "setup.rb"] + + spec.require_path = "lib" + + spec.test_file = "test/ts_all.rb" + + spec.has_rdoc = true + spec.extra_rdoc_files = %w{README LICENSE TODO AUTHORS CHANGELOG} + spec.rdoc_options << '--title' << 'Ruport Documentation' << + '--main' << 'README' << '-q' + unless LEAN + spec.add_dependency('fastercsv', '>= 0.1.0') + spec.add_dependency('RedCloth', '>= 3.0.0') + spec.add_dependency('pdf-writer', '>= 1.1.3') + end + spec.author = "Gregory Brown" + spec.email = " gregory.t.brown@gmail.com" + spec.rubyforge_project = "ruport" + spec.homepage = "http://reporting.stonecode.org" + spec.description = <
+ +h2. Loading CSVs + +
+ +In work, I pretty much munge CSVs day in and day out. It was indeed the +original reason why I created Ruport. This recipe will show you some of the +common CSV procedures that I've been using. + +h3. Basic Functionality + +Ruby's built-in CSV support isn't that bad. If you just wanted to pull in a +CSV and do some basic manipulations on it, it may be more than enough +for your needs. + +Here's a small example of the minimal CSV code you might encounter: +

+  require "csv"
+  res = CSV.read "foo.csv"
+
+ +This would load the contents of "foo.csv" into a two dimensional array. +The tools available to you in Ruby's Array class are extensive, but if you're +like me you get tired of writing the same annoying code to do simple things like +drop the third column. + +Typically, this is the way I'd do that in pure ruby: + +

+  res.map! { |r| r.delete_at(2) }
+
+ +Now this isn't really super duper ugly, but when it gets thrown in the mix with +other manipulations, you start to wish the code read very explicitly. Also, a +ton of maps seems to violate DRY after a while, even if you are doing different +manipulations each time. + +So here is the equivalant Ruport code that would load in a CSV and drop the +third column: + +

+  require "ruport"
+
+  a = Ruport::DataSet.load("foo.csv")
+  a.remove_columns!(2)
+
+ +This kind of syntactic sugar isn't all the functionality you'll get out of using +Ruport's DataSet loading, but it sure is sweet :) + +h3. Access By Field Names + +Ruport automatically takes the first row of your CSV and uses it to define field +names. This means you can access your data by field name as well as ordinal +position. + +So for example, have a look at the DataSet below. + +

+  foo,bar,baz
+  1,2,3
+  4,5,6
+
+ +Once loaded into a DataSet, my_loaded_data["bar"] +and my_loaded_data[1] become equivalent. + +If you have CSV data that does not have field names, you can tell Ruport not to +look for them like this: + +

+a = Ruport::DataSet.load("foo.csv", :has_names => false)
+
+ +h3. Default Values for Empty Cells + +Empty cells will automatically be converted to nil. However, if you'd like +them to be converted to some other value, you can pass this via the +:default option. + +

+a = Ruport::DataSet.load("foo.csv", :default => "empty cell")
+
+ +h3. Dealing With Big Files + +Ruport doesn't use the standard library's CSV processing library, but instead uses +James Edward Gray II's fine FasterCSV library (http://fastercsv.rubyforge.org) + +This library is over 8 times faster than the standard CSV lib, which means that +even though your results are converted into DataSets, you will see a much smaller +performance hit than if Ruport used the standard library beneath it. + +Sometimes when dealing with very big CSVs, it's necessary to do row by row +processing for performance reasons. This makes it possible to create DataRows +for only the data you're actually interested in. + +Below is a snippet of row by row processing for DataSet creation from CSV. The +block yields two objects, the first is the DataSet which will represent your +data, and the second is an array representing the current row. This is useful +for inspecting the Arrays to see if you're interested in the data before +generating a DataRow for them. The snippet below will create a DataSet which +consists of just rows in which the first column is an even number. + +

+  require "ruport"
+
+  a = Ruport::DataSet.load("foo.csv") do |s,r|
+    s << r if (r[0].to_i % 2).zero?
+  end
+
+ +So, this about sums up all of Ruport's CSV loading capabilities. If you see +anything missing that you'd like to see included in Ruport, let me know. + +h2. Talking to SQL Databases + +
+ +Most reporting jobs involve querying one or more databases. Ruport provides a +high level interface on top of Ruby DBI to help make getting results from a SQL +database easier. DBI allows Ruport to talk to many popular database systems, +including MySQL, Postgres, SQLite, and Oracle. Personally, I use MySQL for +developing and testing Ruport's SQL functionality, and I have implemented a +number of applications which run against MS SQL Server via ODBC. These recipes +will be based on MySQL because this is the most well tested platform Ruport +supports. However, these examples should work with minimal modifications on +most database systems DBI supports. + +h3. Configuration + +Ruport allows for a number of different database configurations to be used +concurrently. This is made easy through a simple configuration system. If you +wanted to connect to the mysql test database using the root user and no password, the +config would look something like this: + +

+  Ruport.configure { |c|                                                         
+    c.source :default, :dsn => "dbi:mysql:test", :user => "root"               
+  } 
+
+ +The :dsn attribute is a Ruby DBI DSN, and should work as expecting +for whatever DBD you are using. These are often in the form +dbi:my_dbd_name:my_database_name:my_host_name, though certain DBDs +have special forms, so you should consult the Ruby DBI documentation if you have +trouble. + +Ruport's Query class will look for the default source if you do not specify a +source, but it is very easy to tell it to access other sources, so feel free to +define as many as you'd like. + +Below is an example of loading a database via ODBC as the default, and a MySQL +database as :test. We will show later how to select particular +databases when you do your queries. + +

+  Ruport.configure { |c|
+    c.source :default, :dsn => "dbi:odbc:clyde", 
+                       :user => "blinky", :password => "123"
+    c.source :test,    :dsn => "dbi:mysql:test", :user => "root"
+  }
+
+ +h3. Basic Functionality + +The basic SQL capabilities of Ruport are very similar to DBI, except at a higher +level. One thing that Ruport can handle that DBI cannot is multi-statement SQL +strings, which is important for loading things such as database dumps. Another +feature of Ruport's query model is that it supports treating result sets as if +they were regular enumerable objects, which can be very useful. + +The general use case will be to generate a DataSet from a SQL query's results. +This can then be used in all the ways a regular Ruport DataSet can be used. This +is quite easy to do. + +Assuming you already have a default source set, the example below shows how to +run a trivial query and get back a DataSet. + +

+  my_data_set = Ruport::Query.new("select * from foo").result
+
+ +Sometimes you might want to load a complex query fom a file. Ruport's query +model allows this simply by setting the :origin + +

+  my_data_set = Ruport::Query.new("some_sql_file.sql",:origin => :file).result
+
+ +You can also treat queries as enumerable objects, which will yield DataRows by +default. + +The bit of code below will return an array of the values for "some_field" in +each row of the result set. + +

+  Ruport::Query.new("select * from foo").map { |r| r["some_field"] }
+
+ +Note that by default, these query objects will run the query each time either an +Enumerable method or result/execute is called. + +If you want to tie the query object to a certain data set temporarily, you can +use caching, which will be explained later. + +h3. Selecting sources + +The Query object will try to use the :default source specified either in +Ruport::Config or via Ruport.configure when you do your queries. + +However, sometimes you might want to use the same query for different sources. +Recall before that we had created a source called :test in the +exact same manner as the :default source. + +To build a Query object that will use this :test source, we simply +specify a keyword: + +

+
+  a = Ruport::Query.new("select * from foo",:source => :test)
+
+
+ +If we wanted to swap sources midstream, we can easily do that. The code below +sets the source back to :default. + +

+
+  a.select_source :default
+
+
+ +Finally, if we didn't want to use Ruport's configuration system, we can specify +a DSN and username / password directly. + +

+
+  b = Ruport::Query.new("select * from foo",:dsn => "dbi:mysql:my_db", 
+                                            :user => "root", :password => "cat")
+
+ +h3. Caching + +Ruport's Query object has simple caching capabilities which may come in handy if +you want to speed up access to the same result set to be used in a number of +ways. Think of a cached query object as a sort of 'dataset generator'. + +When caching is enabled, the next set of data which is loaded through the query +object will be stored, and this set of data will be returned to you for each +subsequent method you call on the query object, until you turn caching off. + +The following bit of code creates a Query object and enables caching: + +

+  q = Ruport::Query.new("select * from foo", :cache_enabled => true)
+
+ +Now, the first time we run our query, this will load our data which will stick +there until we clear the cache or disable caching. + +So you can feel free to use Query as you normally would, but the query will only +be run once. From here, there are a few more things to note about caching. + +If you want to clear out the cache so that the next method called on the object +will grab new data: + +

+  q.clear_cache
+
+ +However, if you want to immediately update your cached data with fresh results: + +

+  q.update_cache
+
+ +Which of the two you use depends on what you are doing, but the key thing to +remember is that clear_cache will simply empty out the cache, it +will not update the data until you run a method that tries to pull the results. +On the other hand update_cache will immediately update your result +set. + +You can easily disable caching when you are done with it. Note that this will +flush the cached data. + +

+  q.disable_caching
+
+ +Likewise, turning it back on is an easy operation as well: + +

+  q.enable_caching
+
+ +Honestly, I have not made much use of this caching capability, but it seems as +if it could come in handy for certain scenarios. If you are using this +functionality for something, and like it, please let me know what you are doing +so I can make a nice recipe for it. If you hate this idea, also let me know. ;) + +h3. Generators + +Sometimes, you might want to have an external iterator for a result set. +Perhaps you want to be able to inspect a record, and then do some other +operations, and eventually move on to the next record. + +This technically could be done within an each iterator or by +popping values off of a dataset or array, but what if you want to be able to +easily rewind to the beginning of a result set? + +This is where a Generator proves to be useful. Be sure to check out the docs in +the Ruby standard library (ri Generator), but here is a short example of how to grab a +generator from a result set and how to use it. + + +

+  #builds a generator from the result set
+  gen = Ruport::Query.new("select * from foo").generator
+
+  #grabs the first row
+  row1 = gen.next
+
+  #grabs the second row
+  row2 = gen.next
+
+  #rewinds the generator
+  gen.rewind
+  
+
+ +One thing to beware of, if you reach the end of a result set and try to run +Generator#next, an EOFError will be thrown. To avoid this, you can +do something like this: + +

+  gen.next if gen.next?
+
+ +This will return the row if it is available, otherwise, it will return nil. + +Please note that although Generators can be handy, they are quite slow. This is +because they are implemented using continuations. So if performance is a +concern, you should probably use some other way of iterating through your data, +and you should probably also read the next section. + +For the adventurous, Generator is much faster in Ruby 1.9. For the slightly +less adventurous, you can probably hotswap a solution to RubyQuiz 66 +(http://rubyquiz.com/quiz66.html) if you really want to get wild and crazy. +For the not so adventurous, forget you read this paragraph :) + +h3. Optimizing for Performance + +The first thing to note is that SQL will (almost) always be faster than any ruby +manipulations you can do. This means that for big or tough queries, the best +route to fast results is some good SQL. Eventually, Ruport will have some sort +of DSL to help you with this, but for now, we trust you to know your SQL. ;) + +Assuming you've got some decent SQL, and you still need to optimize performance, +you should probably never, ever try to build a DataSet with greater +than 5 or 10k records. This performance issue may be overcome in the future if I +see sufficient need for it in the community, but for now, DataSets are really not +meant to handle that much data quickly. + +Therefore, you will probably want to use row by row operations. This way you +can quickly throw out data you are uninterested in before you ever stick it in a +DataSet. + +However, you'll also want to strip down the rows to something more lightweight. +DBI::Rows are currently the lightest it gets using Ruport, which when used, +pretty much has the same efficiency as Ruby DBI itself. + +The following example shows how you can build a DataSet using DBI::Rows as the +base and doing some conditional logic that will help lighten the load. + +

+  
+  #create a query object and tell it to yield DBI::Rows by default
+  a = Ruport::Query.new("select * from foo",:raw_data => true)
+ 
+  #set up a dataset specifying only the fields I want to capture
+  data = Ruport::DataSet.new %w[fields i am interested in]
+
+  #append the data to my set if the "name" field starts with a lowercase 'g'
+  a.inject(data) { |s,r|
+    s << r if r["name"] =~ /^g/ ; s
+  }
+
+ +This code attacks efficiency in a lot of ways. First of all, it throws out any +uninteresting columns, saving space and time. Secondly, it does a conditional +before you load data into your DataSet, which is much better than converting all +of the data and then dropping some rows. Finally, it uses DBI::Rows instead of +DataRows for its iterator, ensuring that a much lighter structure is used for +comparison. + +This should result in a hefty performance gain. + +However, if you are *really* concerned about speed / size, you're going to want +to roll your own DBI code. The current implementation of Query is still doing a +fetch_all under the hood, which may be really slow for massive sets. This will +likely be changed in upcoming versions of Ruport. + +However, since it's usually Ruport, and not DBI that is the bottleneck, this +sort of technique will probably be sufficient in most cases where you need a +decent speed boost for medium to fairly large size result sets. + +This basically sums up the Query class and its functionality. I'll be happy to +consider feedback, so please be sure to contact me if you have any needs that +aren't being met or suggestions on how to improve this functionality. + +h2. ActiveRecord support + +
+ +Ruby Reports has some basic support for integration with ActiveRecord. Because +we didn't want to reinvent the AR/Rails wheel, the support is basically a +minimal set of tools for working within a Rails app. Note that although you +can still use Ruport as a normal library throughout Rails, this section will +focus specifically on the features Ruport adds to ActiveRecord through the +acts_as_reportable system. + +Ruport's rails support gives you a nice ActiveRecord#to_ds which +allows you to convert a model or a find on a model into a DataSet. From there, +you are free to use all of Ruport's data manipulation tools. + +It also provides some formatting shortcuts + +h3. Enabling acts_as_reportable + +In order to get Ruport to talk to Rails, not much needs to be done. Really, +it's just two special lines of code. Ruport does not enable Rails support +by default, so you need to turn it on if you want to use it. + +To enable the rails features, put require 'ruport/rails' in your +environment.rb file in your rails app. + +If you are using the ActiveRecord support somewhere else, perhaps in Camping, +just put this line wherever you do normal requires. + +The next step is to hook up Ruport to whatever models you'd like: + +

+  class User < ActiveRecord::Base
+    acts_as_reportable
+  end
+
+ +If you want to enable Ruport support in all models, you can do this: + +

+  class ActiveRecord::Base
+    acts_as_reportable
+  end
+
+ +Be forewarned however, that might be overzealous + +h3. Easy DataSet Conversion + +If you've got a relatively small model and you need most of the data from it to +do some reporting, it couldn't get easier to convert it into a DataSet: + +

+  User.to_ds
+
+ +You also can set the columns of the DataSet here, if you'd like. + +

+  User.to_ds :columns => %w[ name email ]
+
+ +Note that a lot of times it's useful to wait until formatting your table to +remove various columns. Many times you end up wanting to do something with +columns you do not plan on displaying. + +h3. Using ActiveRecord.find + +For dealing with models which wrap a lot of data, sucking the entire table into +a DataSet would be costly (and most likely painful). + +Therefore, you can freely use ActiveRecord's find method on the fly while you +are converting. To do this, pass whatever options you would pass +ActiveRecord.find via the :find keyword: + +

+  User.to_ds :find => { :conditions => [ "name = ?", "greg" ] }
+
+ +The find will be executed first and then the results will be converted to a +DataSet. + +h3. Formatting Shortcuts + +Ruport will also define a formatted_table method which you might find helpful in +your application helpers. This is almost equivalent to: + +

+  some_data_set.as(:format_type) { |engine| ... } 
+
+ +The difference is you can take advantage of AR finds. Here are some examples +of this feature and how it may be used. + +Dump all the users as an HTML table: +

+  User.formatted_table :html
+
+ +Create a CSV file containing all the records that have an email address +containing 'foo', put in order of the id column: + +

+  User.formatted_table :csv, 
+    :find => { :conditions => "email like '%foo%'", :order => 'id' }
+
+ +Create a CSV file containing all the records that have an email address +containing 'foo' using 'with_scope', put in order of the id column: + +

+  User.with_scope( :find => {:conditions => "email like'%foo%'"}) {
+    User.formatted_table :html, :find => { :order => 'id'} 
+  }
+
+ +Create a CSV file containing all the records that have an email address +containing 'foo', put in order of the id column, display only the id +and email columns: + +

+  User.formatted_table :csv, :columns => %w[id email], 
+    :find => { :conditions => "email like '%foo%'", :order => 'id' }                                      
+
+ +Keep in mind that your finds are just ActiveRecord finds and the results are +just the same as Ruport's DataSets and Table outputs. This means you are free +to use the best of both AR and Ruport to accomplish your tasks. diff --git a/doc/recipes/data_set_manipulation.textile b/doc/recipes/data_set_manipulation.textile new file mode 100644 index 00000000..f114bc6b --- /dev/null +++ b/doc/recipes/data_set_manipulation.textile @@ -0,0 +1,226 @@ +h1. DataSet Manipulation + +h2. Basic functionality + +
+You don't always have to get your DataSets from a database or a csv file. +You can make your own from scratch: + +

+  ghost = Ruport::DataSet.new(%w[nickname, color])
+
+ +And then add some data: + +

+  ghost << ["blinky", "red"]
+
+ +You can make a copy of a DataSet using the clone method. There's +also an empty_clone method which gives you a DataSet with the +same fields as the one you're copying, but with no data. + +The << method tries to do the Right Thing depending on what you +are trying to add to your DataSet. You saw above that you can use an array. A +hash also works: + +

+  ghost << {:color => "pink", :nickname => "pinky"}
+
+ +Or a DataRow: +

+  inky = DataRow.new(%w[nickname, color], :data => %w[inky blue] )
+  ghost << inky
+
+ +Or even another DataSet: +

+  lesser_known = ghost.empty_clone 
+  lesser_known << %w[funky green]
+  lesser_known << %w[spunky gray]
+  ghost << lesser_known
+
+ +DataSets are similar to arrays, and have many of the same methods. +length,[], empty?, +delete_at, first, last, +pop, each, reverse_each, +at, and clear all work just like their counterparts +in Array. + +h2. Set-like Operations + +
+ +Using the set operations in DataSet, you can manipulate sets of similar data +to get what you're looking for. These methods only work if the DataSets have +the same fields, which you can check with same_shape?. Keep in +mind that these methods ignore duplicate rows, so each record in the result +set is unique. + +We'll use these two DataSets that we've already gotten from a query on our +database of great characters in literature: + +

+pacman:
+  inky, blue
+  blinky, red
+  pinky, pink
+  clyde, orange
+
+ms_pacman:
+  inky, blue
+  blinky, red
+  pinky, pink
+  sue, orange
+
+ +To find the rows that two DataSets have in common, you can use the set +intersection method, & : + +

+  common_ghosts = pacman & ms_pacman
+
+ +This will give you a new DataSet, common_ghosts, which consists +of each row that appears in both of our original sets: + +

+  inky, blue
+  blinky, red
+  pinky, pink
+
+ +If you want the rows that are in either of two DataSets, use the set union +method, | : + +

+  all_ghosts = pacman | ms_pacman
+
+ +Which gives us: +

+  inky, blue
+  blinky, red
+  pinky, pink
+  clyde, orange
+  sue, orange
+
+ +Finally, we can take the difference of two sets with, you guessed it, the set +difference method, - . This method takes the first set and +deletes from it each of the rows in the second set. For example: + +

+  pacman_only = pacman - ms_pacman
+
+ +giving us: +

+  clyde, orange
+
+ +A lot of the times, these manipulations will be exactly what you need to get +your data organized in a reportable form. However, there are situations where +you need something a little less structured, and the next section covers exactly +that. + +h2. Using Tags + +
+ +Sometimes, you might want mark a row with some sort of flag to be used later on +down the line. One of the key goals of Ruport is to allow you to use the same +data to produce a number of different reports. + +One tool to help you do this is row based tagging. Let's say for instance you +had a bunch of data that included phone numbers. If you wanted to mark all the +ones that were missing an entry in that column, you might do something like +this: + +

+  my_data.each { |r| r.tag_as :missing_number if r["phone"].to_s.empty? }
+
+ + +Note that this didn't do anything to actually change your data. It also doesn't +tie any logic to the tags, it simply applies them if the condition is met. + +That means that you or someone else who knows that the :missing_number tag has +been applied to the data doesn't need to know at all the criteria of the +labeling nor the actual structure of the data. All they need to do is search +for the tag: + +

+  my_data.select { |r| r.has_tag? :missing_number }
+
+ +This sort of abstraction is useful when you may have several data sources with +different conditions that determine how they will be tagged. + +h2. Calculated fields + +
+ +There are two types of calculated fields to consider when using DataSets: + +# Something that is only going to be calculated once or when it's explicitly +told to be calculated. +# Something that is going to be calculated every time it is rendered. + +Unfortunately there is no inbuilt support for calculated fields just yet, so you +need to roll your own. I've written a recipe for each case which shows how I +handle this kind of thing. + +h3. Static Calculated Fields + +When dealing with fields you want to have stick around for a while, +the easiest thing to do is just create a new column to hold your calculations. + +

+
+  my_data.add_columns! "new_col"
+
+
+ +You could then just fill in this column with whatever calculated data you +wanted. Below I set the new_col column to be equal to the first column's value ++ 1 for each row: + +

+  
+  my_data.each { |r| r["new_col"] = r[0] + 1 }
+
+
+ +This approach is good for calculations you're only going to do when the DataSet +is populated or explicitly told to update. + +h3. Calculated Fields "On the fly" + +Sometimes, you're going to need to generate some fields in a table every time it +is rendered as formatted output. It's going to be a common task to rewrite +certain fields on the fly, so there is a convenience method for it in the table +rendering engine. + +Let's assume that we've got the same goal as before, to add an additional column +to the DataSet and then do a quick calculation on it. + +The code below does more or less the same thing as before, but at format time +instead of DataSet building time. It will not modify your original object. + +

+my_data.as(:html) do |eng|
+  engine.data.add_columns! "new_col"
+  engine.rewrite_column("new_col") { |row| row[0] + 1 }
+end
+
+ +As you can probably guess, rewrite_column passes in the rows one at a +time and then reassigns the column you specify's cell to the return value of the +block. + +Even though this process should probably be more tightly integrated, it's +certainly doable by the procedures above. + diff --git a/doc/recipes/email.textile b/doc/recipes/email.textile new file mode 100644 index 00000000..65d534c1 --- /dev/null +++ b/doc/recipes/email.textile @@ -0,0 +1,19 @@ +h1. Emailing Reports + +
+ +h2. Basic Usage + +
+ +
+ +h2. Attachments and HTML + +
+ +
+ +h2. 3rd party mail objects + +
diff --git a/doc/recipes/extending_ruport.textile b/doc/recipes/extending_ruport.textile new file mode 100644 index 00000000..67781d4a --- /dev/null +++ b/doc/recipes/extending_ruport.textile @@ -0,0 +1,20 @@ +h1. Extending Ruport + +
+ +h2. Format Engine Plugins + +
+ +
+ +h2. Custom Format Engines + +
+ +
+ +h2. Black Magic & Voodoo + +
+ diff --git a/doc/recipes/formatting_system.textile b/doc/recipes/formatting_system.textile new file mode 100644 index 00000000..a88ffdf2 --- /dev/null +++ b/doc/recipes/formatting_system.textile @@ -0,0 +1,15 @@ +h1. Formatting System + +
+ +h2. Tabular Output + +
+ +h3. Pretty Field Names + +
+ +h2. Structured Documents + +
diff --git a/doc/recipes/resources.textile b/doc/recipes/resources.textile new file mode 100644 index 00000000..f84b646a --- /dev/null +++ b/doc/recipes/resources.textile @@ -0,0 +1,60 @@ +h1. Resources + +h2. General Links + +
+ +* Project Page + + http://rubyforge.org/projects/ruport/ + +* Downloads + + http://rubyforge.org/frs/?group_id=856 + +* Subversion Browser + + http://rubyforge.org/scm/?group_id=856 + +h2. Community + +
+ +* Wiki and Blog + + http://ruport.infogami.com/ + + "http://ruport.infogami.com/blog":http://ruport.infogami.com + +* Mailing List + + + http://lists.stonecode.org/listinfo.cgi/ruport-stonecode.org + +* IRC Channel + + #ruport on Freenode + +h2. Learning about Ruport + +
+ +* API Documentation + + http://reporting.stonecode.org/docs/ + +* Tutorials + + http://ruport.infogami.com/Tutorials + + +h2. Reporting Problems + +
+ +* Bug List / Tracker + + "http://ruport.infogami.com/Bug_List":http://ruport.infogami.com/Bug_List + +
+ diff --git a/doc/ruport-milestone-0.png b/doc/ruport-milestone-0.png new file mode 100644 index 0000000000000000000000000000000000000000..316ca7d69e77fec11643422ebe68a6a073290171 GIT binary patch literal 47797 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 literal 0 HcmV?d00001 diff --git a/doc/ruport_recipes.textile b/doc/ruport_recipes.textile new file mode 100644 index 00000000..5c8aa56d --- /dev/null +++ b/doc/ruport_recipes.textile @@ -0,0 +1,58 @@ +<% def chapter(c); File.read("recipes/#{c}.textile"); end %> +h1. Introduction + +Thank you for taking the time to learn about Ruby Reports. + +In a nutshell, Ruport is a free software library that is designed +specifically to make the task of reporting less painful. I've been developing a +number of toolsets to make my life easier, and I hope that they can do this for +you as well. + +Some of the functionality Ruport currently provides include: + +* Data acquisition modules that allow loading data from sources + including CSV, SQL, and ActiveRecord + +* Data normalization into a standardized and well integrated datastructure. + +* Data formatting including tabular formatting, text filters, and structured documents + +* Data delivery as part of an HTML page, via email, or out to a file. + +Ruport provides an extendable formatting system, so you can add in arbitrary +formatting support as needed. The tools provided can be used in a stand-alone +command-line application, integrated into a Rails application, +or in any other Ruby program. + +Some of the key strengths of Ruport: + +* Data sources are extracted, therefore reports can be generated from +eclectic data sources ranging from text files generated by legacy VMS +applications to data extracted via ActiveRecord. + +* Data normalization happens in a central part of your code so that no +matter the input source or the output format, the data can be accessed through +a consistent interface. + +* Output can be formatted in many different ways allowing for generation of +PDF, HTML, and text based reports from the same data. + +The recipes that follow will help you use Ruport to meet your reporting needs. +They are meant to cover the common problem areas of building a reporting +application and to show how to solve these problems reasonably. Most of these +recipes have been extracted from real world applications, so that means they'll +be focused on actual needs. + +I hope that you find the information you need here to happily complete whatever +task you need to do. If you find yourself stuck, please see the Resources +section at the end of this book for links to a number of great places to go for +help. + +<%= chapter "data_acquisition" %> +<%= chapter "data_set_manipulation" %> +<%= chapter "formatting_system" %> +<%= chapter "email" %> +<%= chapter "extending_ruport" %> +<%= chapter "resources" %> + +h1. Acknowledgements diff --git a/examples/fieldless_table.rb b/examples/fieldless_table.rb new file mode 100644 index 00000000..efbba8f7 --- /dev/null +++ b/examples/fieldless_table.rb @@ -0,0 +1,10 @@ +require "ruport" +include Ruport + + +class Format::Plugin::FieldlessCSVPlugin < Format::Plugin::CSVPlugin + rendering_options :show_field_names => false + plugin_name :fieldless_csv + register_on :table_engine +end + diff --git a/examples/line_plotter.rb b/examples/line_plotter.rb new file mode 100644 index 00000000..de9e0a77 --- /dev/null +++ b/examples/line_plotter.rb @@ -0,0 +1,44 @@ +require "ruport" + +include Ruport + +class LinePlotter < Format::Engine + + Line = Struct.new(:x1,:y1,:x2,:y2) + + renderer do + active_plugin.data = get_lines + active_plugin.render_plot + end + + def self.get_lines + data.map { |r| Line.new(r[0][0],r[0][1],r[1][0],r[1][1]) } + end + + alias_engine LinePlotter, :line_plotting_engine + Format.build_interface_for LinePlotter, :plot +end + +class SVG < Format::Plugin + + renderer :plot do + h = ''+ + '' + + data.inject(h) { |s,r| + s << "" + } << "" + end + + register_on :line_plotting_engine +end + +lines = [ [ [0,0], [0,100] ], + [ [0,100], [100,100] ], + [ [100,100], [100,0] ], + [ [100,0], [0,0] ] ] + +a = Format.plot :plugin => :svg, + :data => lines +puts a diff --git a/examples/long.txt b/examples/long.txt new file mode 100644 index 00000000..fb6804bd --- /dev/null +++ b/examples/long.txt @@ -0,0 +1,24 @@ +TWO roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; 5 + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, 10 + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. 15 + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. 20 + diff --git a/examples/new_plugin.rb b/examples/new_plugin.rb new file mode 100644 index 00000000..2400325c --- /dev/null +++ b/examples/new_plugin.rb @@ -0,0 +1,21 @@ +require "rubygems" +require "ruport" + +include Ruport + +class Format::Plugin::Text < Ruport::Format::Plugin + renderer :table do + data.inject(rendered_field_names) { |s,r| + s << r.to_a.join("()") << "\n" + } + end + + format_field_names do + data.fields.join("---") << "\n" + end + + register_on :table_engine +end + +puts Format.table({ :data => [[1,2],[3,4]].to_ds(%w[a b]), + :plugin => :text }) diff --git a/examples/simple_mail.rb b/examples/simple_mail.rb new file mode 100644 index 00000000..c056edc5 --- /dev/null +++ b/examples/simple_mail.rb @@ -0,0 +1,15 @@ +require "ruport" + +Ruport.configure do |conf| + conf.mailer :default, + :host => "mail.adelphia.net", :address => "gregory.t.brown@gmail.com" +end + +mailer = Ruport::Mailer.new + +mailer.attach "README" + +mailer.deliver :to => "gregory.t.brown@gmail.com", + :from => "gregory.t.brown@gmail.com", + :subject => "Hey there", + :text => "This is what you asked for" diff --git a/lib/ruport.rb b/lib/ruport.rb new file mode 100644 index 00000000..b2661f78 --- /dev/null +++ b/lib/ruport.rb @@ -0,0 +1,65 @@ +# ruport.rb : Ruby Reports toplevel module +# +# Author: Gregory T. Brown (gregory.t.brown at gmail dot com) +# +# Copyright (c) 2006, All Rights Reserved. +# +# This is free software. You may modify and redistribute this freely under +# your choice of the GNU General Public License or the Ruby License. +# +# See LICENSE and COPYING for details +# + +module Ruport + + begin; require 'rubygems'; rescue LoadError; nil end + + VERSION = "Ruby Reports Version 0.4.13" + + # Ruports logging and error interface. + # Can generate warnings or raise fatal errors + # + # Takes a message to display and a set of options. + # Will log to the file defined by Config::log_file + # + # Options: + # :status:: sets the severity level. defaults to :warn + # :output:: optional 2nd output, defaults to $stderr + # :level:: set to :log_only to disable secondary output + # :exception:: exception to throw on fail. Defaults to RunTimeError + # + # The status :warn will invoke Logger#warn. A status of + # :fatal will invoke Logger#fatal and raise an exception + # + # By default, complain will also print warnings to $stderr + # You can redirect this to any I/O object via :output + # + # You can prevent messages from appearing on the secondary output by setting + # :level to :log_only + # + # If you want to recover these messages to secondary output for debugging, you + # can use Config::enable_paranoia + def Ruport.complain(message,options={}) + options[:status] ||= :warn + options[:output] ||= $stderr + options[:output].puts "[!!] #{message}" unless + options[:level].eql?(:log_only) and not Ruport::Config.paranoid? + case(options[:status]) + when :warn + Ruport::Config::logger.warn(message) if Ruport::Config.logger + when :fatal + Ruport::Config::logger.fatal(message) if Ruport::Config.logger + raise options[:exception] || RuntimeError, message + end + end + + def Ruport.configure(&block) + block.call(Ruport::Config) + end + +end + + +%w[config report format query data_row data_set].each { |lib| + require "ruport/#{lib}" +} diff --git a/lib/ruport/config.rb b/lib/ruport/config.rb new file mode 100644 index 00000000..d8ddb2e5 --- /dev/null +++ b/lib/ruport/config.rb @@ -0,0 +1,127 @@ +# ruport/config.rb : Ruby Reports configuration system +# +# Author: Gregory T. Brown (gregory.t.brown at gmail dot com) +# +# Copyright (c) 2006, All Rights Reserved. +# +# This is free software. You may modify and redistribute this freely under +# your choice of the GNU General Public License or the Ruby License. +# +# See LICENSE and COPYING for details +# +require "singleton" +require "ostruct" +module Ruport + # This class serves as the configuration system for Ruport. + # It's functionality is implemented through Config::method_missing + # + # source :default and mailer :default will become the fallback values if one + # is not specified in Report::Mailer or Query, but you may define as many + # sources as you like and switch between them later. + # + # An example config file is shown below: + # + # # password is optional, dsn may omit hostname for localhost + # Ruport::Config.source :default, + # :dsn => "dbi:mysql:somedb:db.blixy.org", + # :user => "root", :password => "chunky_bacon" + # + # # :password, :port, and :auth_type are optional. :port defaults to 25 and + # # :auth_type defaults to :plain. For more information, see the source + # # of Report::Mailer#select_mailer + # Ruport::Config.mailer :default, + # :host => "mail.chunkybacon.org", :address => "chunky@bacon.net", + # :user => "cartoon", :password => "fox", :port => 25, :auth_type => :login + # + # # optional, if specifed, Ruport#complain will report to it + # Ruport::Config.log_file 'foo.log' + # + # # optional, if enabled, will force :log_only complaint calls to + # # print to secondary output ($sterr by default). + # # call Ruport::Config.disable_paranoia to disable + # Ruport::Config.enable_paranoia + # + # Alternatively, this configuration could be done by opening the class: + # class Ruport::Config + # + # source :default, :dsn => "dbi:mysql:some_db", :user => "root" + # + # mailer :default, :host => "mail.iheartwhy.com", + # :address => "sandal@ruby-harmonix.net", :user => "sandal", + # :password => "abc123" + # + # logfile 'foo.log' + # + # end + # + # Saving this config information into a file and then requiring it can allow + # you share configurations between Ruport applications. + # + class Config + include Singleton + + def Config.method_missing(method_id,*args) + case(method_id) + when :source + return @@sources[args.first] if args.length == 1 + @@sources[args.first] = OpenStruct.new(*args[1..-1]) + check_source(@@sources[args.first],args.first) + when :mailer + @@mailers[args.first] = OpenStruct.new(*args[1..-1]) + check_mailer(@@mailers[args.first],args.first) + when :log_file + @@logger = Logger.new(args.first) + when :default_source + @@sources[:default] + when :default_mailer + @@mailers[:default] + when :sources + @@sources + when :mailers + @@mailers + when :logger + @@logger + when :enable_paranoia + @@paranoid = true + when :disable_paranoia + @@paranoid = false + when :paranoid? + @@paranoid + else + super + end + end + + private + + def Config.init! + @@sources = { } + @@mailers = { } + @@logger = nil + @@paranoid = false + end + + def Config.check_source(settings,label) + unless settings.dsn + Ruport.complain( + "Missing DSN for source #{label}!", + :status => :fatal, :level => :log_only, + :exception => ArgumentError + ) + end + end + + def Config.check_mailer(settings, label) + unless settings.host + Ruport.complain( + "Missing host for mailer #{label}", + :status => :fatal, :level => :log_only, + :exception => ArgumentError + ) + end + end + + init! + + end +end diff --git a/lib/ruport/data_row.rb b/lib/ruport/data_row.rb new file mode 100644 index 00000000..5a5609d4 --- /dev/null +++ b/lib/ruport/data_row.rb @@ -0,0 +1,181 @@ +# -- +# data_row.rb : Ruby Reports row abstraction +# +# Author: Gregory T. Brown (gregory.t.brown at gmail dot com) +# +# Copyright (c) 2006, All Rights Reserved. +# +# This is free software. You may modify and redistribute this freely under +# your choice of the GNU General Public License or the Ruby License. +# +# See LICENSE and COPYING for details +# ++ +module Ruport + + # DataRows are Enumerable lists which can be accessed by field name or + # ordinal position. + # + # They feature a tagging system, allowing them to be easily + # compared or recalled. + # + # DataRows form the elements of DataSets + # + class DataRow + + include Enumerable + + # Takes field names as well as some optional parameters and + # constructs a DataRow. + # + # Options: + # :data:: can be specified in Hash, Array, or DataRow form + # :default:: The default value for empty fields + # :tags:: an initial set of tags for the row + # + # + # Examples: + # >> Ruport::DataRow.new [:a,:b,:c,:d,:e], :data => [1,2,3,4,5] + # :tags => %w[cat dog] + # => # + # + # >> Ruport::DataRow.new([:a,:b,:c,:d,:e], + # :data => { :a => 'moo', :c => 'caw'} , + # :tags => %w[cat dog]) + # => # + # + # >> Ruport::DataRow.new [:a,:b,:c,:d,:e], :data => [1,2,3], + # :tags => %w[cat dog], :default => 0 + # => # + # + def initialize(fields=nil, options={}) + + #checks to ensure data is convertable + verify options[:data] + data = options[:data].dup + + @fields = fields ? fields.dup : ( 0...data.length ).to_a + @tags = (options[:tags] || {}).dup + @data = [] + + nr_action = case(data) + when Array + lambda {|key, index| @data[index] = data.shift || options[:default]} + when DataRow + lambda {|key, index| @data = data.to_a} + else + lambda {|key, index| @data[index] = data[key] || options[:default]} + end + @fields.each_with_index {|key, index| nr_action.call(key,index)} + end + + + attr_accessor :fields, :tags + alias_method :column_names, :fields + + # Returns a new DataRow + def +(other) + DataRow.new @fields + other.fields, :data => (@data + other.to_a) + end + + # Lets you access individual fields + # + # i.e. row["phone"] or row[4] + def [](key) + case(key) + when Fixnum + @data[key] + when Symbol + @data[@fields.index(key.to_s)] rescue nil + else + @data[@fields.index(key)] rescue nil + end + end + + # Lets you set field values + # + # i.e. row["phone"] = '2038291203', row[7] = "allen" + def []=(key,value) + case(key) + when Fixnum + @data[key] = value + when Symbol + @data[@fields.index(key.to_s)] = value + else + @data[@fields.index(key)] = value + end + end + + # Converts the DataRow to a plain old Array + def to_a + @data.clone + end + + def to_h + a = Hash.new + @fields.each { |f| a[f] = self[f] }; a + end + + # Converts the DataRow to a string representation + # for outputting to screen. + def to_s + "[" + @data.join(",") + "]" + end + + # Checks to see row includes the tag given. + # + # Example: + # + # >> row.has_tag? :running_balance + # => true + # + def has_tag?(tag) + @tags.include?(tag) + end + + # Iterates through DataRow elements. Accepts a block. + def each(&action) + @data.each(&action) + end + + # Allows you to add a tag to a row. + # + # Examples: + # + # row.tag_as(:jay_cross) if row["product"].eql?("im_courier") + # row.tag_as(:running_balance) if row.fields.include?("RB") + # + def tag_as(something) + @tags[something] = true + end + + # Compares two DataRow objects. If values and fields are the same + # (and in the correct order) returns true. Otherwise returns false. + def ==(other) + self.to_a.eql?(other.to_a) && @fields.eql?(other.fields) + end + + # Synonym for DataRow#== + def eql?(other) + self == other + end + + def clone + self.class.new @fields, :data => @data, :tags => @tags + end + + alias_method :dup, :clone + + private + + def verify(data) + if data.kind_of? String or + data.kind_of? Integer or not data.respond_to?(:[]) + Ruport.complain "Cannot convert data to DataRow", + :status => :fatal, :exception => ArgumentError, :level => :log_only + end + end + end +end diff --git a/lib/ruport/data_set.rb b/lib/ruport/data_set.rb new file mode 100644 index 00000000..03e935d5 --- /dev/null +++ b/lib/ruport/data_set.rb @@ -0,0 +1,299 @@ +# data_set.rb : Ruby Reports core datastructure. +# +# Author: Gregory T. Brown (gregory.t.brown at gmail dot com) +# Copyright (c) 2006, All Rights Reserved. +# +# Pseudo keyword argument support, improved <<, and set operations submitted by +# Dudley Flanders +# +# This is free software. You may modify and redistribute this freely under +# your choice of the GNU General Public License or the Ruby License. +# +# See LICENSE and COPYING for details +module Ruport + + # The DataSet is the core datastructure for Ruport. It provides methods that + # allow you to compare and combine query results, data loaded in from CSVs, + # and user-defined sets of data. + # + # It is tightly integrated with Ruport's formatting and query systems, so if + # you'd like to take advantage of these models, you will probably find + # DataSet useful. + class DataSet + #FIXME: Add logging to this class + include Enumerable + extend Forwardable + # DataSets must be given a set of fields to be defined. + # + # These field names will define the columns for the DataSet and how you + # access them. + # + # data = Ruport::DataSet.new %w[ id name phone ] + # + # Options: + # * :data - An Enumerable with the content for this DataSet + # * :default - The default value for empty cells + # + # + # DataSet supports the following Array methods through delegators + # length, empty?, delete_at, first, last, pop + # + def initialize(fields=nil, options={}) + @fields = fields.dup if fields + @default = options[:default] || default + @data = [] + options[:data].each { |r| self << r } if options[:data] + end + + #an array which contains column names + attr_accessor :fields + alias_method :column_names, :fields + + #the default value to fill empty cells with + attr_accessor :default + + #data holds the elements of the Row + attr_reader :data + + def_delegators :@data, :length, :[], :empty?, + :delete_at, :first, :last, :pop, + :each, :reverse_each, :at, :clear + + def delete_if(&block) + @data.delete_if █ self + end + + #provides a deep copy of the DataSet. + def clone + self.class.new(@fields, :data => @data) + end + + alias_method :dup, :clone + + # Creates a new DataSet with the same shape as this one, but empty. + def empty_clone + self.class.new(@fields) + end + + #allows setting of rows + def []=(index,data) + @data[index] = DataRow.new @fields, :data => data + end + + def [](index) + case(index) + when Range + self.class.new @fields, :data => @data[index] + else + @data[index] + end + end + + # Appends a row to the DataSet + # Can be added as an array, an array of DataRows, a DataRow, or a keyed + # hash-like object. + # + # Columns left undefined will be filled with DataSet#default values. + # + # data << [ 1, 2, 3 ] + # data << { :some_field_name => 3, :other => 2, :another => 1 } + # + # FIXME: Appending a datarow is wonky. + def << ( stuff, filler=@default ) + if stuff.kind_of?(DataRow) + @data << stuff.clone + elsif stuff.kind_of?(Array) && stuff[0].kind_of?(DataRow) + @data.concat(stuff) + else + @data << DataRow.new(@fields, :data => stuff.clone,:default => filler) + end + return self + end + alias_method :push, :<< + + # checks if one dataset equals another + # FIXME: expand this doc. + def eql?(data2) + return false unless ( @data.length == data2.data.length and + @fields.eql?(data2.fields) ) + @data.each_with_index do |row, r_index| + row.each_with_index do |field, f_index| + return false unless field.eql?(data2[r_index][f_index]) + end + end + + return true + end + + # checks if one dataset equals another + def ==(data2) + eql?(data2) + end + + # Set union - returns a DataSet with the elements that are contained in + # in either of the two given sets, with no duplicates. + def |(other) + clone << other.reject { |x| self.include? x } + end + alias_method :union, :| + + # Set intersection + def &(other) + empty_clone << select { |x| other.include? x } + end + alias_method :intersection, :& + + # Set difference + def -(other) + empty_clone << reject { |x| other.include? x } + end + alias_method :difference, :- + + # Checks if one DataSet has the same set of fields as another + def shaped_like?(other) + return true if @fields.eql?(other.fields) + end + + # Concatenates one DataSet onto another if they have the same shape + def concat(other) + if other.shaped_like?(self) + @data.concat(other.data) + return self + end + end + alias_method :+, :concat + + # Allows loading of CSV files or YAML dumps. Returns a DataSet + # + # FasterCSV will be used if it is installed. + # + # my_data = Ruport::DataSet.load("foo.csv") + # my_data = Ruport::DataSet.load("foo.yaml") + # my_data = Ruport::DataSet.load("foo.yml") + def self.load ( source, options={}, &block) + options = {:has_names => true}.merge(options) + case source + when /\.(yaml|yml)/ + return YAML.load(File.open(source)) + when /\.csv/ + require "fastercsv" + input = FasterCSV.read(source) if source =~ /\.csv/ + loaded_data = self.new + + action = if block_given? + lambda { |r| block[loaded_data,r] } + else + lambda { |r| loaded_data << r } + end + + loaded_data.fields = input[0] if options[:has_names] + input = input[1..-1] if options[:has_names] + + loaded_data.default = options[:default] + input.each { |row| action[row] } + return loaded_data + else + raise "Invalid file type" + end + end + + + # Returns a new DataSet composed of the fields specified. + def select_columns(*fields) + fields = get_field_names(fields) + rows = fields.inject([]) { |s,e| s + [map { |row| row[e] }] }.transpose + my_data = DataSet.new(fields, :data => rows) + end + + # Prunes the dataset to contain only the fields specified. (DESTRUCTIVE) + def select_columns!(*fields) + a = select_columns(*fields) + @fields = a.fields; @data = a.data + end + + #Creates a new dataset with additional columns appending to it + def add_columns(*fields) + select_columns *(@fields + fields) + end + + def add_columns!(*fields) + select_columns! *(@fields + fields) + end + + # Returns a new DataSet with the specified fields removed + def remove_columns(*fields) + fields = get_field_names(fields) + select_columns(*(@fields-fields)) + end + + # removes the specified fields from this DataSet (DESTRUCTIVE!) + def remove_columns!(*fields) + d = remove_columns(*fields) + @data = d.data + @fields = d.fields + end + + # uses Format::Builder to render DataSets in various ready to output + # formats. + # + # data.as(:html) -> String + # + # data.as(:text) do |builder| + # builder.range = 2..4 -> String + # builder.header = "My Title" + # end + # + # To add new formats to this function, simply re-open Format::Builder + # and add methods like render_my_format_name. + # + # This will enable data.as(:my_format_name) + def as(format,&action) + t = Format.table_object(:data => clone, :plugin => format) + action.call(t) if block_given? + t.render + end + + # Will iterate row by row yielding each row + # The result of the block will be added to a running total + # + # Only works with blocks resulting in numeric values. + def sigma + inject(0) do |s,r| + s + (yield(r) || 0) + end + end + + alias_method :sum, :sigma + + # Converts a DataSet to an array of arrays + def to_a + @data.map {|x| x.to_a } + end + + # Converts a DataSet to CSV + def to_csv; as(:csv) end + + # Converts a Dataset to html + def to_html; as(:html) end + + # Readable string representation of the DataSet + def to_s; as(:text) end + + private + + def get_field_names(f) + f.all? { |e| e.kind_of? Integer } && + f.inject([]) { |s,e| s + [@fields[e]] } || f + end + + end +end + +class Array + + # Will convert Arrays of Enumerable objects to DataSets. + # May have dragons. + def to_ds(fields,options={}) + Ruport::DataSet.new fields, :data => to_a, :default => options[:default] + end +end diff --git a/lib/ruport/format.rb b/lib/ruport/format.rb new file mode 100644 index 00000000..2b8f0061 --- /dev/null +++ b/lib/ruport/format.rb @@ -0,0 +1,151 @@ +# format.rb : Ruby Reports formatting module +# +# Author: Gregory T. Brown (gregory.t.brown at gmail dot com) +# +# Copyright (c) 2006, All Rights Reserved. +# +# This is free software. You may modify and redistribute this freely under +# your choice of the GNU General Public License or the Ruby License. +# +# See LICENSE and COPYING for details +module Ruport + + +# Ruport's Format model is meant to help get your data in a suitable format for +# output. Rather than make too many assumptions about how you will want your +# data to look, a number of tools have been built so that you can quickly define +# those things yourself. +# +# There are three main sets of functionality the Ruport::Format model provides. +# * Structured printable document support ( Format::Document and friends) +# * Text filter support ( Report#render and the Format class) +# * Support for DataSet Formatting ( Format::Builder) +# +# The support for structured printable documents is currently geared towards PDF +# support and needs some additional work to be truly useful. Suggestions would +# be much appreciated. +# +# Format::Builder lets you define functions that will be used via DataSet#as +# This is primary geared towards tabular data output, but there is no reason why +# DataSet#as and the render_foo methods of Format::Builder cannot be +# adapted to fit whatever needs you may need. +# +# The filters implemented in the Format class are meant to process strings or +# entire templates. The Format class will soon automatically build a +# Ruport::Parser for any string input. By default, filters are provided to +# process erb, pure ruby, and redcloth. It is trivial to extend this +# functionality though. +# +# This is best shown by a simple example: +# +# a = Ruport::Report.new +# Ruport::Format.register_filter :reverser do +# content.reverse +# end +# a.render "somestring", :filters => [:reverser] +# +# Output: "gnirtsemos" +# +# Filters can be combined, and you can run them in different orders to obtain +# different results. +# +# See the source for the built in filters for ideas. +# +# Also, see Report#render for how to bind Format objects to your own classes. +# +# When combined, filters, data set output templates, and structured printable +# document facilities create a complete Formatting system. +# +# This part of Ruport is under active development. Please do feel free to +# submit feature requests or suggestions. + class Format + + def Format.build_interface_for(engine,name) + (class << self; self; end).send(:define_method, name, + lambda { |options| simple_interface(engine, options) }) + (class << self; self; end).send(:define_method, "#{name}_object", + lambda { |options| + options[:auto_render] = false; simple_interface(engine,options) }) + end + + %w[open_node document engine plugin].each { |lib| + require "ruport/format/#{lib}" + } + + class << self + + def simple_interface(engine, options={}) + my_engine = engine.dup + + my_engine.send(:plugin=,options[:plugin]) + options = my_engine.active_plugin.rendering_options.merge(options) + + options[:auto_render] = true unless options.has_key? :auto_render + + + options[:data] = options[:data].dup + + options.each do |k,v| + my_engine.send("#{k}=",v) if my_engine.respond_to? k + end + + options[:auto_render] ? my_engine.render : my_engine.dup + end + + end + + @@filters = Hash.new + + # To hook up a Format object to your current class, you need to pass it a + # binding. This way, when filters are being processed, they will be + # evaluated in the context of the object they are being called from, rather + # than within an instance of Format. + # + def initialize(klass_binding) + @binding = klass_binding + end + + # This is the text to be processed by the filters + attr_accessor :content + + # This is the binding to the object Format is tied to + attr_accessor :binding + + # Processes the ERB text in @content in the context + # of the object that Format is bound to. + def filter_erb + self.class.document :data => @content, + :klass_binding => @binding, + :plugin => :text + end + + # Processes the RedCloth text in @content in the context + # of the object that Format is bound to. + def filter_red_cloth + self.class.document :data => @content, :plugin => :html + end + + # Takes a name and a block and creates a filter method + # This will define methods in the form of + # Format#filter_my_filter_name. + # + # This code will run as an instance method on Format. + # You can access format and binding through their accessors, + # as well as any other filters. + # + # Example: + # + # Format.register_filter :no_ohz do + # content.gsub(/O/i,"") + # end + def Format.register_filter(name,&filter_proc) + @@filters["filter_#{name}".to_sym] = filter_proc + end + + def method_missing(m) + @@filters[m] ? @@filters[m].call(@content) : super + end + + end +end + diff --git a/lib/ruport/format/document.rb b/lib/ruport/format/document.rb new file mode 100644 index 00000000..b062e1fe --- /dev/null +++ b/lib/ruport/format/document.rb @@ -0,0 +1,78 @@ +# FIXME: Copyright notice +# HERE THERE BE DRAGONS! +require "ostruct" +module Ruport + class Format + class Document < OpenStruct + include Enumerable + + def initialize(name,options={}) + super(options) + self.name = name + self.pages ||= [] + end + + def each + self.pages.each { |p| yield(p) } + end + + def add_page(name,options={}) + options[:document] = self + self.pages << Format::Page.new(name,options) + end + + def <<(page) + page.document = self + self.pages << page.dup + end + + def [](page_name) + return self.pages[page_name] if page_name.kind_of? Integer + self.pages.find { |p| p.name.eql?(page_name) } + end + + def clone + cloned = self.clone + cloned.pages = self.pages.clone + return cloned + end + end + + class Page < Format::OpenNode + + def initialize(name,options={}) + super(:page,:document,:sections,name,options) + end + + def add_section(name,options={}) + add_child(Format::Section,name,options) + end + + end + + class Section < Format::OpenNode + + def initialize(name, options={}) + super(:section,:page,:elements,name,options) + end + + def add_element(name,options={}) + add_child(Format::Element,name,options) + end + + end + + class Element < OpenStruct + + def initialize(name,options={}) + super(options) + self.name = name + end + + def to_s + self.content + end + + end + end +end diff --git a/lib/ruport/format/engine.rb b/lib/ruport/format/engine.rb new file mode 100644 index 00000000..7bbb4b1f --- /dev/null +++ b/lib/ruport/format/engine.rb @@ -0,0 +1,142 @@ +module Ruport + class Format::Engine + require "forwardable" + + class << self + + include Enumerable + extend Forwardable + + def_delegator :@data, :each + + def renderer(&block) + block = lambda { data } unless block_given? + (class << self; self; end).send(:define_method, :render, &block) + end + + attr_accessor :engine_klasses + attr_reader :plugin + attr_reader :data + attr_accessor :klass_binding + + def alias_engine(klass,name) + Format::Engine.engine_klasses ||= {} + Format::Engine.engine_klasses[name] = klass + end + + def data=(data) + @data = data + active_plugin.data = data.dup if active_plugin + end + + def active_plugin + @format_plugins[:current] + #plugin && @format_plugins[plugin] + end + + def plugin=(p) + @plugin = p + @format_plugins[:current] = @format_plugins[p].dup + @format_plugins[:current].data = self.data.dup if self.data + end + + def apply_erb + active_plugin.data = + ERB.new(active_plugin.data).result(klass_binding || binding) + end + + def render + raise "No plugin specified" unless plugin + raise "No data provided" unless data + active_plugin.data = data.dup + end + + def flush_data + self.data = nil + end + + def accept_format_plugin(klass) + format_plugins[klass.format_name] = klass + end + + private + + def format_plugins + @format_plugins ||= {} + end + + def plugin_names + format_plugins.keys + end + + def plugins + format_plugins.values + end + + end + end + + class Format::Engine::Table < Format::Engine + + renderer do + super + active_plugin.rendered_field_names = "" + build_field_names if (data.respond_to?(:fields) && + data.fields && show_field_names) + a = active_plugin.render_table + end + + class << self + + def rewrite_column(key,&block) + data.each { |r| r[key] = block[r] } + end + + def num_cols + data[0].to_a.length + end + + attr_accessor :show_field_names + + private + + def build_field_names + if active_plugin.respond_to?(:build_field_names) + active_plugin.rendered_field_names = active_plugin.build_field_names + end + end + + end + + alias_engine Table, :table_engine + Format.build_interface_for Table, :table + self.show_field_names = true + end + + class Format::Engine::Document < Format::Engine + + renderer do + super + apply_erb if erb_enabled + apply_red_cloth if red_cloth_enabled + active_plugin.render_document + end + + class << self + + attr_accessor :red_cloth_enabled + attr_accessor :erb_enabled + + def apply_red_cloth + require "redcloth" + active_plugin.data = RedCloth.new(active_plugin.data).to_html + end + + end + + alias_engine Document, :document_engine + Format.build_interface_for Document, :document + end + + +end diff --git a/lib/ruport/format/open_node.rb b/lib/ruport/format/open_node.rb new file mode 100644 index 00000000..ba7a6240 --- /dev/null +++ b/lib/ruport/format/open_node.rb @@ -0,0 +1,38 @@ +#FIXME: COPYRIGHT NOTICE + +require "ostruct" +module Ruport + class Format + class OpenNode < OpenStruct + include Enumerable + def initialize(my_name, parent_name, children_name, name, options={}) + @my_children_name = children_name + @my_parent_name = parent_name + @my_name = my_name + super(options) + self.name = name + self.send(@my_children_name) || + self.send("#{@my_children_name}=".to_sym,{}) + end + + def each &p + self.send(@my_children_name).values.each(&p) + end + + def add_child(klass,name,options={}) + options[@my_name] = self + self << klass.new(name, options) + end + + def <<(child) + child.send("#{@my_name}=".to_sym, self) + self.send(@my_children_name)[child.name] = child.dup + end + + def [](child_name) + self.send(@my_children_name)[child_name] + end + + end + end +end diff --git a/lib/ruport/format/plugin.rb b/lib/ruport/format/plugin.rb new file mode 100644 index 00000000..a8b47db7 --- /dev/null +++ b/lib/ruport/format/plugin.rb @@ -0,0 +1,176 @@ +module Ruport + class Format::Plugin + + class << self + + attr_accessor :data + + def plugin_name(name) + @name = name + end + + def format_name + pattern = /Ruport::Format|Plugin/ + @name ||= + self.name.gsub(pattern,"").downcase.delete(":").to_sym + end + + def renderer(render_type,&block) + m = "render_#{render_type}".to_sym + block = lambda { data } unless block_given? + (class << self; self; end).send(:define_method, m, &block) + end + + def format_field_names(&block) + (class << self; self; end).send(:define_method, :build_field_names, &block) + end + + def register_on(klass) + + if klass.kind_of? Symbol + klass = Format::Engine.engine_klasses[klass] + end + + klass.accept_format_plugin(self) + end + + def rendering_options(hash={}) + @options ||= {} + @options.merge!(hash) + @options.dup + end + + attr_accessor :rendered_field_names + attr_accessor :pre, :post + end + + + class CSVPlugin < Format::Plugin + + format_field_names do + require "fastercsv" + FasterCSV.generate { |csv| csv << data.fields } + end + + renderer :table do + require "fastercsv" + rendered_field_names + + FasterCSV.generate { |csv| data.each { |r| csv << r } } + end + + register_on :table_engine + end + + class TextPlugin < Format::Plugin + rendering_options :erb_enabled => true, :red_cloth_enabled => false + + renderer :document + + renderer :table do + require "ruport/system_extensions" + + th = "#{rendered_field_names}#{hr}" + + data.each { |r| + r.each_with_index { |f,i| + r[i] = f.to_s.center(max_col_width(i)) + } + } + + a = data.inject(th){ |s,r| + s + "| #{r.to_a.join(' | ')} |\n" + } << hr + + width = self.right_margin || SystemExtensions.terminal_width + + a.split("\n").each { |r| + r.gsub!(/\A.{#{width},}/) { |m| m[0,width-2] += ">>" } + }.join("\n") << "\n" + end + format_field_names do + data.fields.each_with_index { |f,i| + data.fields[i] = f.to_s.center(max_col_width(i)) + } + "#{hr}| #{data.fields.to_a.join(' | ')} |\n" + end + + def self.max_col_width(index) + f = data.fields if data.respond_to? :fields + d = DataSet.new f, :data => data + + cw = d.map { |r| r[index].to_s.length }.max + + return cw unless d.fields + + nw = (index.kind_of?(Integer) ? d.fields[index] : index ).to_s.length + + [cw,nw].max + end + + def self.table_width + f = data.fields if data.respond_to? :fields + d = DataSet.new f, :data => data + + d[0].fields.inject(0) { |s,e| s + max_col_width(e) } + end + + def self.hr + len = data[0].to_a.length * 3 + table_width + 1 + "+" + "-"*(len-2) + "+\n" + end + + class << self; attr_accessor :right_margin; end + + register_on :table_engine + register_on :document_engine + end + + class PDFPlugin < Format::Plugin + + renderer :table do + require "pdf/writer"; require "pdf/simpletable"; + pdf = PDF::Writer.new + pre[pdf] if pre + PDF::SimpleTable.new do |table| + table.maximum_width = 500 + table.orientation = :center + table.data = data + m = "Sorry, cant build PDFs from array like things (yet)" + raise m if self.rendered_field_names.empty? + table.column_order = self.rendered_field_names + table.render_on(pdf) + end + post[pdf] if post + pdf.render + end + + format_field_names { data.fields } + + register_on :table_engine + end + + class HTMLPlugin < Format::Plugin + + rendering_options :red_cloth_enabled => true, :erb_enabled => true + + renderer :document + + renderer :table do + rc = data.inject(rendered_field_names) { |s,r| + row = r.map { |e| e.to_s.empty? ? " " : e } + s + "|#{row.to_a.join('|')}|\n" + } + Format.document :data => rc, :plugin => :html + end + + format_field_names do + s = "|_." + data.fields.join(" |_.") + "|\n" + end + + register_on :table_engine + register_on :document_engine + + end + + end +end diff --git a/lib/ruport/mailer.rb b/lib/ruport/mailer.rb new file mode 100644 index 00000000..cc2b2cbc --- /dev/null +++ b/lib/ruport/mailer.rb @@ -0,0 +1,50 @@ +# mailer.rb +# Created by Gregory Brown on 2005-08-16 +# Copyright 2005 (Gregory Brown) All Rights Reserved. +# This product is free software, you may distribute it as such +# under your choice of the Ruby license or the GNU GPL +# See LICENSE for details +require "net/smtp" +require "forwardable" +module Ruport + class Mailer + extend Forwardable + + def initialize( mailer_label=:default ) + select_mailer(mailer_label); mail_object + rescue + raise "you need to specify a mailer to use" + end + + def_delegators( :@mail, :to, :to=, :from, :from=, + :subject, :subject=, :attach, + :text, :text=, :html, :html= ) + + def deliver(options={}) + options.each { |k,v| send("#{k}=",v) if respond_to? "#{k}=" } + + Net::SMTP.start(@host,@port,@host,@user,@password,@auth) do |smtp| + smtp.send_message((options[:mail_object] || mail_object).to_s, options[:from], options[:to] ) + end + end + + def select_mailer(label) + mailer = Ruport::Config.mailers[label] + @host = mailer.host + @user = mailer.user + @password = mailer.password + @address = mailer.address + @port = mailer.port || 25 + @auth = mailer.auth_type || :plain + @mail_klass = mailer.mail_klass + end + + def mail_object + return @mail if @mail + return @mail ||= @mail_klass.new if @mail_klass + require "mailfactory" + @mail ||= MailFactory.new + end + + end +end diff --git a/lib/ruport/query.rb b/lib/ruport/query.rb new file mode 100644 index 00000000..6a041146 --- /dev/null +++ b/lib/ruport/query.rb @@ -0,0 +1,207 @@ +require "generator" +require "ruport/query/sql_split" + +module Ruport + + # Query offers a way to interact with databases via DBI. It supports + # returning result sets in either Ruport's native DataSets, or in their raw + # form as DBI::Rows. + # + # It offers basic caching support, the ability to instantiate a generator for + # a result set, and the ability to quickly and easily swap between data + # sources. + class Query + + + include Enumerable + + # Queries are initialized with some SQL and a number of options that effect + # their operation. They are NOT executed at initialization. + # + # This is important to note as they will not query the database until either + # Query#result, Query#execute, Query#generator, or an enumerable method is + # called on them. + # + # This kind of laziness is supposed to be A Good Thing, and + # as long as you keep it in mind, it should not cause any problems. + # + # The SQL can be single or multistatement, but the resulting DataSet will + # consist only of the result of the last statement which returns something. + # + # Options: + # + # :source + # A source specified in Ruport::Config.sources, defaults to :default + # :origin + # query origin, default to :string, but can be + # set to :file, loading the path specified by the sql parameter + # :dsn + # If specifed, the query object will manually override Ruport::Config + # :user + # If a DSN is specified, a user can be set by this option + # :password + # If a DSN is specified, a password can be set by this option + # :raw_data + # When set to true, DBI::Rows will be returned + # :cache_enabled + # When set to true, Query will download results only once, and then return + # cached values until cache has been cleared. + # Examples: + # + # # uses Ruport::Config's default source + # Ruport::Query.new("select * from fo") + # + # # uses the Ruport::Config's source labeled :my_source + # Ruport::Query.new("select * from fo", :source => :my_source) + # + # # uses a manually entered source + # Ruport::Query.new("select * from fo", :dsn => "dbi:mysql:my_db", + # :user => "greg", :password => "chunky_bacon" ) + # + # # uses a SQL file stored on disk + # Ruport::Query.new("my_query.sql",:origin => :file) + def initialize(sql, options={}) + options[:source] ||= :default + options[:origin] ||= :string + @sql = sql + @statements = SqlSplit.new(get_query(options[:origin],sql)) + + if options[:dsn] + Ruport::Config.source :temp, :dsn => options[:dsn], + :user => options[:user], + :password => options[:password] + options[:source] = :temp + end + + select_source(options[:source]) + + @raw_data = options[:raw_data] + @cache_enabled = options[:cache_enabled] + @cached_data = nil + end + + # set to true to get DBI:Rows, false to get Ruport constructs + attr_accessor :raw_data + + # modifying this might be useful for testing, this is the data stored by + # ruport when caching + attr_accessor :cached_data + + # this is the original SQL for the Query object + attr_reader :sql + + # This will set the dsn, username, and password to one specified by a label + # that corresponds to a source in Ruport::Config + def select_source(label) + @dsn = Ruport::Config.sources[label].dsn + @user = Ruport::Config.sources[label].user + @password = Ruport::Config.sources[label].password + end + + # Standard each iterator, iterates through result set row by row. + def each(&action) + Ruport::complain( + "no block given!", :status => :fatal, + :level => :log_only, :exception => LocalJumpError + ) unless action + fetch &action + end + + # Grabs the result set as a DataSet or if in raw_data mode, an array of + # DBI:Row objects + def result; fetch; end + + # Runs the query without returning its results. + def execute; fetch; nil; end + + # clears the contents of the cache + def clear_cache + @cached_data = nil + end + + # clears the contents of the cache and then runs the query, filling the + # cache with the new result + def update_cache + clear_cache + caching_flag,@cache_enabled = @cache_enabled, true + fetch; @cache_enabled = caching_flag + end + + # Turns on caching. New data will not be loaded until cache is clear or + # caching is disabled. + def enable_caching + @cache_enabled = true + end + + # Turns off caching and flushes the cached data + def disable_caching + @cached_data = nil + @cache_enabled = false + end + + # Returns a DataSet, even if in raw_data mode + # Does not work with raw data if cache is enabled and filled + def to_dataset + data_flag, @raw_data = @raw_data, false + data = fetch; @raw_data = data_flag; return data + end + + # Returns a csv dump of the query + def to_csv + to_dataset.to_csv + end + + # Returns a Generator object of the result set + def generator + Generator.new(fetch) + end + + private + + def query_data( query_text ) + + require "dbi" + + data = @raw_data ? [] : DataSet.new + DBI.connect(@dsn, @user, @password) do |dbh| + dbh.execute(query_text) do |sth| + return unless sth.fetchable? + results = sth.fetch_all + data.fields = sth.column_names unless @raw_data + results.each { |row| data << row } + end + end + data + rescue NoMethodError; nil + end + + def get_query(type,query) + case (type) + when :string + query + when :file + load_file( query ) + end + end + + def load_file( query_file ) + begin; File.read( query_file ).strip ; rescue + Ruport::complain "Could not open #{query_file}", + :status => :fatal, :exception => LoadError + end + end + + def fetch(&action) + data = nil + if @cache_enabled and @cached_data + data = @cached_data + else + @statements.each { |query_text| data = query_data( query_text ) } + end + data.each { |r| action.call(r) } if block_given? ; data + @cached_data = data if @cache_enabled + return data + end + + end +end diff --git a/lib/ruport/query/sql_split.rb b/lib/ruport/query/sql_split.rb new file mode 100644 index 00000000..150b6b06 --- /dev/null +++ b/lib/ruport/query/sql_split.rb @@ -0,0 +1,33 @@ +#-- +# sqlsplit.rb : A tool to properly split SQL input +# +# This is Free Software. You may freely redistribute or modify under the terms +# of the GNU General Public License or the Ruby License. See LICENSE and +# COPYING for details. +# +# Created by Francis Hwang, 2005.12.31 +# Copyright (c) 2005, All Rights Reserved. +#++ +module Ruport + class Query + # This class properly splits up multi-statement SQL input for use with + # Ruby/DBI + class SqlSplit < Array + def initialize( sql ) + super() + next_sql = '' + sql.each do |line| + unless line =~ /^--/ or line =~ %r{^/\*.*\*/;} or line =~ /^\s*$/ + next_sql << line + if line =~ /;$/ + next_sql.gsub!( /;\s$/, '' ) + self << next_sql + next_sql = '' + end + end + end + self << next_sql if next_sql != '' + end + end + end +end diff --git a/lib/ruport/rails.rb b/lib/ruport/rails.rb new file mode 100644 index 00000000..e4c6bd45 --- /dev/null +++ b/lib/ruport/rails.rb @@ -0,0 +1,2 @@ +require "ruport" +require "ruport/rails/reportable" diff --git a/lib/ruport/rails/reportable.rb b/lib/ruport/rails/reportable.rb new file mode 100644 index 00000000..50ecf2f2 --- /dev/null +++ b/lib/ruport/rails/reportable.rb @@ -0,0 +1,32 @@ +module Ruport + module Reportable + def formatted_table(type,options={},&block) + to_ds(:find => options[:find],:columns => options[:columns]).as(type){ |e| + block[e] if block_given? + } + end + def to_ds(options={}) + options[:columns] ||= column_names + find(:all,options[:find]). + to_ds(column_names).select_columns(*options[:columns]) + end + end + class DataSet + alias_method :old_append, :<< + def <<( stuff, filler=@default ) + if stuff.kind_of?(ActiveRecord::Base) + @data << stuff.attributes + else + old_append(stuff,filler) + end + end + end +end + +class ActiveRecord::Base + def self.acts_as_reportable + extend Ruport::Reportable + end +end + + diff --git a/lib/ruport/report.rb b/lib/ruport/report.rb new file mode 100644 index 00000000..51ef62fa --- /dev/null +++ b/lib/ruport/report.rb @@ -0,0 +1,88 @@ +# fixME: Copyright notice here. + +#load the needed standard libraries. +%w[erb yaml date logger fileutils].each { |lib| require lib } + +require "ruport/mailer" + +module Ruport + class Report + def initialize( source_name=:default, mailer_name=:default ) + @source = source_name + @report_name = @report = "" + @file = nil + end + + attr_accessor :file,:report + + # High level interface to Ruport::Query + # - Can read SQL statements from file or string + # - Can use multistatement SQL + # - Can iterate by row or return entire set + # - Can return raw DBI:Row objects or Ruport constructs. + # + # Defaults to returning entire sets of Ruport constructs. + # + # See source of this function and methods of Ruport::Query for details. + def query(sql, options={}, &action) + options[:origin] ||= :string + options[:source] ||= @source + + q = Query.new(sql, options) + if options[:yield_type].eql?(:by_row) + q.each { |r| action.call(r) } + else + block_given? ? action.call(q.result) : q.result + end + end + + # Evaluates _code_ from _filename_ as pure ruby code for files ending in + # .rb, and as ERb templates for anything else. + def eval_template( filename, code ) + filename =~ /\.rb/ ? eval(code) : ERB.new(code, 0, "%").run(binding) + end + + + # Generates the report. If @pre or @post are defined with lambdas, + # they will be called before and after the main code. + # + # If @file != nil, ruport will print to the + # file with the specified name. Otherwise, it will print to STDOUT by + # default. + # + # The source for this function is probably easier to read than this + # explanation, so you may want to start there. + def generate_report + @pre.call if @pre + @file ? File.open(@file,"w") { |f| f.puts @report } : puts(@report) + @post.call if @post + end + + # sets the active source to the Ruport::Config source requested by label. + def use_source(label) + @source = label + end + + # Provides a nice way to execute templates and filters. + # + # Example: + # + # my_report.render( "_<%= @some_internal_var %>_", + # :filters => [:erb,:red_cloth] ) + # + # This method automatically passes a binding into the filters, so you are + # free to access data from your Report instance in your templates. + def render(string, options) + options[:filters].each do |f| + format = Format.new(binding) + format.content = string + string = format.send("filter_#{f}") + end + string + end + + + end +end + + diff --git a/lib/ruport/system_extensions.rb b/lib/ruport/system_extensions.rb new file mode 100644 index 00000000..52eb6738 --- /dev/null +++ b/lib/ruport/system_extensions.rb @@ -0,0 +1,125 @@ +#!/usr/local/bin/ruby -w + +# system_extensions.rb +# +# lovingly ganked from HighLine 1.2.1 +# +# The following modifications have been made by Gregory Brown on 2006.06.25 +# +# - Outer Module is changed from HighLine to Ruport +# - terminal_width / terminal_height added +# +# All modifications are under the distributions terms of Ruport. +# Copyright 2006, Gregory Brown. All Rights Reserved +# +# Original copyright notice preserved below. +# -------------------------------------------------------------------------- +# +# Created by James Edward Gray II on 2006-06-14. +# Copyright 2006 Gray Productions. All rights reserved. +# +# This is Free Software. See LICENSE and COPYING for details. + +module Ruport + module SystemExtensions + module_function + + # + # This section builds character reading and terminal size functions + # to suit the proper platform we're running on. Be warned: Here be + # dragons! + # + begin + require "Win32API" # See if we're on Windows. + + CHARACTER_MODE = "Win32API" # For Debugging purposes only. + + # + # Windows savvy getc(). + # + # *WARNING*: This method ignores input and reads one + # character from +STDIN+! + # + def get_character( input = STDIN ) + Win32API.new("crtdll", "_getch", [ ], "L").Call + end + + # A Windows savvy method to fetch the console columns, and rows. + def terminal_size + m_GetStdHandle = Win32API.new( 'kernel32', + 'GetStdHandle', + ['L'], + 'L' ) + m_GetConsoleScreenBufferInfo = Win32API.new( + 'kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L' + ) + + format = 'SSSSSssssSS' + buf = ([0] * format.size).pack(format) + stdout_handle = m_GetStdHandle.call(0xFFFFFFF5) + + m_GetConsoleScreenBufferInfo.call(stdout_handle, buf) + bufx, bufy, curx, cury, wattr, + left, top, right, bottom, maxx, maxy = buf.unpack(format) + return right - left + 1, bottom - top + 1 + end + rescue LoadError # If we're not on Windows try... + begin + require "termios" # Unix, first choice. + + CHARACTER_MODE = "termios" # For Debugging purposes only. + + # + # Unix savvy getc(). (First choice.) + # + # *WARNING*: This method requires the "termios" library! + # + def get_character( input = STDIN ) + old_settings = Termios.getattr(input) + + new_settings = old_settings.dup + new_settings.c_lflag &= ~(Termios::ECHO | Termios::ICANON) + + begin + Termios.setattr(input, Termios::TCSANOW, new_settings) + input.getc + ensure + Termios.setattr(input, Termios::TCSANOW, old_settings) + end + end + rescue LoadError # If our first choice fails, default. + CHARACTER_MODE = "stty" # For Debugging purposes only. + + # + # Unix savvy getc(). (Second choice.) + # + # *WARNING*: This method requires the external "stty" program! + # + def get_character( input = STDIN ) + state = `stty -g` + + begin + system "stty raw -echo cbreak" + input.getc + ensure + system "stty #{state}" + end + end + end + + # A Unix savvy method to fetch the console columns, and rows. + def terminal_size + `stty size`.split.map { |x| x.to_i }.reverse + end + + def terminal_width + terminal_size.first + end + + def terminal_height + terminal_size.last + end + + end + end +end diff --git a/lib/uport.rb b/lib/uport.rb new file mode 100644 index 00000000..3467f71b --- /dev/null +++ b/lib/uport.rb @@ -0,0 +1 @@ +require "ruport" diff --git a/setup.rb b/setup.rb new file mode 100644 index 00000000..424a5f37 --- /dev/null +++ b/setup.rb @@ -0,0 +1,1585 @@ +# +# setup.rb +# +# Copyright (c) 2000-2005 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public License version 2.1. +# + +unless Enumerable.method_defined?(:map) # Ruby 1.4.6 + module Enumerable + alias map collect + end +end + +unless File.respond_to?(:read) # Ruby 1.6 + def File.read(fname) + open(fname) {|f| + return f.read + } + end +end + +unless Errno.const_defined?(:ENOTEMPTY) # Windows? + module Errno + class ENOTEMPTY + # We do not raise this exception, implementation is not needed. + end + end +end + +def File.binread(fname) + open(fname, 'rb') {|f| + return f.read + } +end + +# for corrupted Windows' stat(2) +def File.dir?(path) + File.directory?((path[-1,1] == '/') ? path : path + '/') +end + + +class ConfigTable + + include Enumerable + + def initialize(rbconfig) + @rbconfig = rbconfig + @items = [] + @table = {} + # options + @install_prefix = nil + @config_opt = nil + @verbose = true + @no_harm = false + end + + attr_accessor :install_prefix + attr_accessor :config_opt + + attr_writer :verbose + + def verbose? + @verbose + end + + attr_writer :no_harm + + def no_harm? + @no_harm + end + + def [](key) + lookup(key).resolve(self) + end + + def []=(key, val) + lookup(key).set val + end + + def names + @items.map {|i| i.name } + end + + def each(&block) + @items.each(&block) + end + + def key?(name) + @table.key?(name) + end + + def lookup(name) + @table[name] or setup_rb_error "no such config item: #{name}" + end + + def add(item) + @items.push item + @table[item.name] = item + end + + def remove(name) + item = lookup(name) + @items.delete_if {|i| i.name == name } + @table.delete_if {|name, i| i.name == name } + item + end + + def load_script(path, inst = nil) + if File.file?(path) + MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path + end + end + + def savefile + '.config' + end + + def load_savefile + begin + File.foreach(savefile()) do |line| + k, v = *line.split(/=/, 2) + self[k] = v.strip + end + rescue Errno::ENOENT + setup_rb_error $!.message + "\n#{File.basename($0)} config first" + end + end + + def save + @items.each {|i| i.value } + File.open(savefile(), 'w') {|f| + @items.each do |i| + f.printf "%s=%s\n", i.name, i.value if i.value? and i.value + end + } + end + + def load_standard_entries + standard_entries(@rbconfig).each do |ent| + add ent + end + end + + def standard_entries(rbconfig) + c = rbconfig + + rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + if c['rubylibdir'] + # V > 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = c['rubylibdir'] + librubyverarch = c['archdir'] + siteruby = c['sitedir'] + siterubyver = c['sitelibdir'] + siterubyverarch = c['sitearchdir'] + elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = c['sitedir'] + siterubyver = "$siteruby/#{version}" + siterubyverarch = "$siterubyver/#{c['arch']}" + else + # V < 1.4.4 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" + siterubyver = siteruby + siterubyverarch = "$siterubyver/#{c['arch']}" + end + parameterize = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') + } + + if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] + else + makeprog = 'make' + end + + [ + ExecItem.new('installdirs', 'std/site/home', + 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ + {|val, table| + case val + when 'std' + table['rbdir'] = '$librubyver' + table['sodir'] = '$librubyverarch' + when 'site' + table['rbdir'] = '$siterubyver' + table['sodir'] = '$siterubyverarch' + when 'home' + setup_rb_error '$HOME was not set' unless ENV['HOME'] + table['prefix'] = ENV['HOME'] + table['rbdir'] = '$libdir/ruby' + table['sodir'] = '$libdir/ruby' + end + }, + PathItem.new('prefix', 'path', c['prefix'], + 'path prefix of target environment'), + PathItem.new('bindir', 'path', parameterize.call(c['bindir']), + 'the directory for commands'), + PathItem.new('libdir', 'path', parameterize.call(c['libdir']), + 'the directory for libraries'), + PathItem.new('datadir', 'path', parameterize.call(c['datadir']), + 'the directory for shared data'), + PathItem.new('mandir', 'path', parameterize.call(c['mandir']), + 'the directory for man pages'), + PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), + 'the directory for system configuration files'), + PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), + 'the directory for local state data'), + PathItem.new('libruby', 'path', libruby, + 'the directory for ruby libraries'), + PathItem.new('librubyver', 'path', librubyver, + 'the directory for standard ruby libraries'), + PathItem.new('librubyverarch', 'path', librubyverarch, + 'the directory for standard ruby extensions'), + PathItem.new('siteruby', 'path', siteruby, + 'the directory for version-independent aux ruby libraries'), + PathItem.new('siterubyver', 'path', siterubyver, + 'the directory for aux ruby libraries'), + PathItem.new('siterubyverarch', 'path', siterubyverarch, + 'the directory for aux ruby binaries'), + PathItem.new('rbdir', 'path', '$siterubyver', + 'the directory for ruby scripts'), + PathItem.new('sodir', 'path', '$siterubyverarch', + 'the directory for ruby extentions'), + PathItem.new('rubypath', 'path', rubypath, + 'the path to set to #! line'), + ProgramItem.new('rubyprog', 'name', rubypath, + 'the ruby program using for installation'), + ProgramItem.new('makeprog', 'name', makeprog, + 'the make program to compile ruby extentions'), + SelectItem.new('shebang', 'all/ruby/never', 'ruby', + 'shebang line (#!) editing mode'), + BoolItem.new('without-ext', 'yes/no', 'no', + 'does not compile/install ruby extentions') + ] + end + private :standard_entries + + def load_multipackage_entries + multipackage_entries().each do |ent| + add ent + end + end + + def multipackage_entries + [ + PackageSelectionItem.new('with', 'name,name...', '', 'ALL', + 'package names that you want to install'), + PackageSelectionItem.new('without', 'name,name...', '', 'NONE', + 'package names that you do not want to install') + ] + end + private :multipackage_entries + + ALIASES = { + 'std-ruby' => 'librubyver', + 'stdruby' => 'librubyver', + 'rubylibdir' => 'librubyver', + 'archdir' => 'librubyverarch', + 'site-ruby-common' => 'siteruby', # For backward compatibility + 'site-ruby' => 'siterubyver', # For backward compatibility + 'bin-dir' => 'bindir', + 'bin-dir' => 'bindir', + 'rb-dir' => 'rbdir', + 'so-dir' => 'sodir', + 'data-dir' => 'datadir', + 'ruby-path' => 'rubypath', + 'ruby-prog' => 'rubyprog', + 'ruby' => 'rubyprog', + 'make-prog' => 'makeprog', + 'make' => 'makeprog' + } + + def fixup + ALIASES.each do |ali, name| + @table[ali] = @table[name] + end + @items.freeze + @table.freeze + @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ + end + + def parse_opt(opt) + m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" + m.to_a[1,2] + end + + def dllext + @rbconfig['DLEXT'] + end + + def value_config?(name) + lookup(name).value? + end + + class Item + def initialize(name, template, default, desc) + @name = name.freeze + @template = template + @value = default + @default = default + @description = desc + end + + attr_reader :name + attr_reader :description + + attr_accessor :default + alias help_default default + + def help_opt + "--#{@name}=#{@template}" + end + + def value? + true + end + + def value + @value + end + + def resolve(table) + @value.gsub(%r<\$([^/]+)>) { table[$1] } + end + + def set(val) + @value = check(val) + end + + private + + def check(val) + setup_rb_error "config: --#{name} requires argument" unless val + val + end + end + + class BoolItem < Item + def config_type + 'bool' + end + + def help_opt + "--#{@name}" + end + + private + + def check(val) + return 'yes' unless val + case val + when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' + when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' + else + setup_rb_error "config: --#{@name} accepts only yes/no for argument" + end + end + end + + class PathItem < Item + def config_type + 'path' + end + + private + + def check(path) + setup_rb_error "config: --#{@name} requires argument" unless path + path[0,1] == '$' ? path : File.expand_path(path) + end + end + + class ProgramItem < Item + def config_type + 'program' + end + end + + class SelectItem < Item + def initialize(name, selection, default, desc) + super + @ok = selection.split('/') + end + + def config_type + 'select' + end + + private + + def check(val) + unless @ok.include?(val.strip) + setup_rb_error "config: use --#{@name}=#{@template} (#{val})" + end + val.strip + end + end + + class ExecItem < Item + def initialize(name, selection, desc, &block) + super name, selection, nil, desc + @ok = selection.split('/') + @action = block + end + + def config_type + 'exec' + end + + def value? + false + end + + def resolve(table) + setup_rb_error "$#{name()} wrongly used as option value" + end + + undef set + + def evaluate(val, table) + v = val.strip.downcase + unless @ok.include?(v) + setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" + end + @action.call v, table + end + end + + class PackageSelectionItem < Item + def initialize(name, template, default, help_default, desc) + super name, template, default, desc + @help_default = help_default + end + + attr_reader :help_default + + def config_type + 'package' + end + + private + + def check(val) + unless File.dir?("packages/#{val}") + setup_rb_error "config: no such package: #{val}" + end + val + end + end + + class MetaConfigEnvironment + def initialize(config, installer) + @config = config + @installer = installer + end + + def config_names + @config.names + end + + def config?(name) + @config.key?(name) + end + + def bool_config?(name) + @config.lookup(name).config_type == 'bool' + end + + def path_config?(name) + @config.lookup(name).config_type == 'path' + end + + def value_config?(name) + @config.lookup(name).config_type != 'exec' + end + + def add_config(item) + @config.add item + end + + def add_bool_config(name, default, desc) + @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + end + + def add_path_config(name, default, desc) + @config.add PathItem.new(name, 'path', default, desc) + end + + def set_config_default(name, default) + @config.lookup(name).default = default + end + + def remove_config(name) + @config.remove(name) + end + + # For only multipackage + def packages + raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer + @installer.packages + end + + # For only multipackage + def declare_packages(list) + raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer + @installer.packages = list + end + end + +end # class ConfigTable + + +# This module requires: #verbose?, #no_harm? +module FileOperations + + def mkdir_p(dirname, prefix = nil) + dirname = prefix + File.expand_path(dirname) if prefix + $stderr.puts "mkdir -p #{dirname}" if verbose? + return if no_harm? + + # Does not check '/', it's too abnormal. + dirs = File.expand_path(dirname).split(%r<(?=/)>) + if /\A[a-z]:\z/i =~ dirs[0] + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless File.dir?(path) + end + end + + def rm_f(path) + $stderr.puts "rm -f #{path}" if verbose? + return if no_harm? + force_remove_file path + end + + def rm_rf(path) + $stderr.puts "rm -rf #{path}" if verbose? + return if no_harm? + remove_tree path + end + + def remove_tree(path) + if File.symlink?(path) + remove_file path + elsif File.dir?(path) + remove_tree0 path + else + force_remove_file path + end + end + + def remove_tree0(path) + Dir.foreach(path) do |ent| + next if ent == '.' + next if ent == '..' + entpath = "#{path}/#{ent}" + if File.symlink?(entpath) + remove_file entpath + elsif File.dir?(entpath) + remove_tree0 entpath + else + force_remove_file entpath + end + end + begin + Dir.rmdir path + rescue Errno::ENOTEMPTY + # directory may not be empty + end + end + + def move_file(src, dest) + force_remove_file dest + begin + File.rename src, dest + rescue + File.open(dest, 'wb') {|f| + f.write File.binread(src) + } + File.chmod File.stat(src).mode, dest + File.unlink src + end + end + + def force_remove_file(path) + begin + remove_file path + rescue + end + end + + def remove_file(path) + File.chmod 0777, path + File.unlink path + end + + def install(from, dest, mode, prefix = nil) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix ? prefix + File.expand_path(dest) : dest + realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) + str = File.binread(from) + if diff?(str, realdest) + verbose_off { + rm_f realdest if File.exist?(realdest) + } + File.open(realdest, 'wb') {|f| + f.write str + } + File.chmod mode, realdest + + File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| + if prefix + f.puts realdest.sub(prefix, '') + else + f.puts realdest + end + } + end + end + + def diff?(new_content, path) + return true unless File.exist?(path) + new_content != File.binread(path) + end + + def command(*args) + $stderr.puts args.join(' ') if verbose? + system(*args) or raise RuntimeError, + "system(#{args.map{|a| a.inspect }.join(' ')}) failed" + end + + def ruby(*args) + command config('rubyprog'), *args + end + + def make(task = nil) + command(*[config('makeprog'), task].compact) + end + + def extdir?(dir) + File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") + end + + def files_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.file?("#{dir}/#{ent}") } + } + end + + DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) + + def directories_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT + } + end + +end + + +# This module requires: #srcdir_root, #objdir_root, #relpath +module HookScriptAPI + + def get_config(key) + @config[key] + end + + alias config get_config + + # obsolete: use metaconfig to change configuration + def set_config(key, val) + @config[key] = val + end + + # + # srcdir/objdir (works only in the package directory) + # + + def curr_srcdir + "#{srcdir_root()}/#{relpath()}" + end + + def curr_objdir + "#{objdir_root()}/#{relpath()}" + end + + def srcfile(path) + "#{curr_srcdir()}/#{path}" + end + + def srcexist?(path) + File.exist?(srcfile(path)) + end + + def srcdirectory?(path) + File.dir?(srcfile(path)) + end + + def srcfile?(path) + File.file?(srcfile(path)) + end + + def srcentries(path = '.') + Dir.open("#{curr_srcdir()}/#{path}") {|d| + return d.to_a - %w(. ..) + } + end + + def srcfiles(path = '.') + srcentries(path).select {|fname| + File.file?(File.join(curr_srcdir(), path, fname)) + } + end + + def srcdirectories(path = '.') + srcentries(path).select {|fname| + File.dir?(File.join(curr_srcdir(), path, fname)) + } + end + +end + + +class ToplevelInstaller + + Version = '3.4.1' + Copyright = 'Copyright (c) 2000-2005 Minero Aoki' + + TASKS = [ + [ 'all', 'do config, setup, then install' ], + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles ruby extentions and others' ], + [ 'install', 'installs files' ], + [ 'test', 'run all tests in test/' ], + [ 'clean', "does `make clean' for each extention" ], + [ 'distclean',"does `make distclean' for each extention" ] + ] + + def ToplevelInstaller.invoke + config = ConfigTable.new(load_rbconfig()) + config.load_standard_entries + config.load_multipackage_entries if multipackage? + config.fixup + klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) + klass.new(File.dirname($0), config).invoke + end + + def ToplevelInstaller.multipackage? + File.dir?(File.dirname($0) + '/packages') + end + + def ToplevelInstaller.load_rbconfig + if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + load File.expand_path(arg.split(/=/, 2)[1]) + $".push 'rbconfig.rb' + else + require 'rbconfig' + end + ::Config::CONFIG + end + + def initialize(ardir_root, config) + @ardir = File.expand_path(ardir_root) + @config = config + # cache + @valid_task_re = nil + end + + def config(key) + @config[key] + end + + def inspect + "#<#{self.class} #{__id__()}>" + end + + def invoke + run_metaconfigs + case task = parsearg_global() + when nil, 'all' + parsearg_config + init_installers + exec_config + exec_setup + exec_install + else + case task + when 'config', 'test' + ; + when 'clean', 'distclean' + @config.load_savefile if File.exist?(@config.savefile) + else + @config.load_savefile + end + __send__ "parsearg_#{task}" + init_installers + __send__ "exec_#{task}" + end + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig" + end + + def init_installers + @installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + # + # Hook Script API bases + # + + def srcdir_root + @ardir + end + + def objdir_root + '.' + end + + def relpath + '.' + end + + # + # Option Parsing + # + + def parsearg_global + while arg = ARGV.shift + case arg + when /\A\w+\z/ + setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) + return arg + when '-q', '--quiet' + @config.verbose = false + when '--verbose' + @config.verbose = true + when '--help' + print_usage $stdout + exit 0 + when '--version' + puts "#{File.basename($0)} version #{Version}" + exit 0 + when '--copyright' + puts Copyright + exit 0 + else + setup_rb_error "unknown global option '#{arg}'" + end + end + nil + end + + def valid_task?(t) + valid_task_re() =~ t + end + + def valid_task_re + @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ + end + + def parsearg_no_options + unless ARGV.empty? + task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) + setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" + end + end + + alias parsearg_show parsearg_no_options + alias parsearg_setup parsearg_no_options + alias parsearg_test parsearg_no_options + alias parsearg_clean parsearg_no_options + alias parsearg_distclean parsearg_no_options + + def parsearg_config + evalopt = [] + set = [] + @config.config_opt = [] + while i = ARGV.shift + if /\A--?\z/ =~ i + @config.config_opt = ARGV.dup + break + end + name, value = *@config.parse_opt(i) + if @config.value_config?(name) + @config[name] = value + else + evalopt.push [name, value] + end + set.push name + end + evalopt.each do |name, value| + @config.lookup(name).evaluate value, @config + end + # Check if configuration is valid + set.each do |n| + @config[n] if @config.value_config?(n) + end + end + + def parsearg_install + @config.no_harm = false + @config.install_prefix = '' + while a = ARGV.shift + case a + when '--no-harm' + @config.no_harm = true + when /\A--prefix=/ + path = a.split(/=/, 2)[1] + path = File.expand_path(path) unless path[0,1] == '/' + @config.install_prefix = path + else + setup_rb_error "install: unknown option #{a}" + end + end + end + + def print_usage(out) + out.puts 'Typical Installation Procedure:' + out.puts " $ ruby #{File.basename $0} config" + out.puts " $ ruby #{File.basename $0} setup" + out.puts " # ruby #{File.basename $0} install (may require root privilege)" + out.puts + out.puts 'Detailed Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-24s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, ' --help', 'print this message' + out.printf fmt, ' --version', 'print version and quit' + out.printf fmt, ' --copyright', 'print copyright and quit' + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf fmt, name, desc + end + + fmt = " %-24s %s [%s]\n" + out.puts + out.puts 'Options for CONFIG or ALL:' + @config.each do |item| + out.printf fmt, item.help_opt, item.description, item.help_default + end + out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" + out.puts + out.puts 'Options for INSTALL:' + out.printf fmt, '--no-harm', 'only display what to do if given', 'off' + out.printf fmt, '--prefix=path', 'install path prefix', '' + out.puts + end + + # + # Task Handlers + # + + def exec_config + @installer.exec_config + @config.save # must be final + end + + def exec_setup + @installer.exec_setup + end + + def exec_install + @installer.exec_install + end + + def exec_test + @installer.exec_test + end + + def exec_show + @config.each do |i| + printf "%-20s %s\n", i.name, i.value if i.value? + end + end + + def exec_clean + @installer.exec_clean + end + + def exec_distclean + @installer.exec_distclean + end + +end # class ToplevelInstaller + + +class ToplevelInstallerMulti < ToplevelInstaller + + include FileOperations + + def initialize(ardir_root, config) + super + @packages = directories_of("#{@ardir}/packages") + raise 'no package exists' if @packages.empty? + @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig", self + @packages.each do |name| + @config.load_script "#{@ardir}/packages/#{name}/metaconfig" + end + end + + attr_reader :packages + + def packages=(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + + def init_installers + @installers = {} + @packages.each do |pack| + @installers[pack] = Installer.new(@config, + "#{@ardir}/packages/#{pack}", + "packages/#{pack}") + end + with = extract_selection(config('with')) + without = extract_selection(config('without')) + @selected = @installers.keys.select {|name| + (with.empty? or with.include?(name)) \ + and not without.include?(name) + } + end + + def extract_selection(list) + a = list.split(/,/) + a.each do |name| + setup_rb_error "no such package: #{name}" unless @installers.key?(name) + end + a + end + + def print_usage(f) + super + f.puts 'Inluded packages:' + f.puts ' ' + @packages.sort.join(' ') + f.puts + end + + # + # Task Handlers + # + + def exec_config + run_hook 'pre-config' + each_selected_installers {|inst| inst.exec_config } + run_hook 'post-config' + @config.save # must be final + end + + def exec_setup + run_hook 'pre-setup' + each_selected_installers {|inst| inst.exec_setup } + run_hook 'post-setup' + end + + def exec_install + run_hook 'pre-install' + each_selected_installers {|inst| inst.exec_install } + run_hook 'post-install' + end + + def exec_test + run_hook 'pre-test' + each_selected_installers {|inst| inst.exec_test } + run_hook 'post-test' + end + + def exec_clean + rm_f @config.savefile + run_hook 'pre-clean' + each_selected_installers {|inst| inst.exec_clean } + run_hook 'post-clean' + end + + def exec_distclean + rm_f @config.savefile + run_hook 'pre-distclean' + each_selected_installers {|inst| inst.exec_distclean } + run_hook 'post-distclean' + end + + # + # lib + # + + def each_selected_installers + Dir.mkdir 'packages' unless File.dir?('packages') + @selected.each do |pack| + $stderr.puts "Processing the package `#{pack}' ..." if verbose? + Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") + Dir.chdir "packages/#{pack}" + yield @installers[pack] + Dir.chdir '../..' + end + end + + def run_hook(id) + @root_installer.run_hook id + end + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + +end # class ToplevelInstallerMulti + + +class Installer + + FILETYPES = %w( bin lib ext data conf man ) + + include FileOperations + include HookScriptAPI + + def initialize(config, srcroot, objroot) + @config = config + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{self.class} #{File.basename(@srcdir)}>" + end + + def noop(rel) + end + + # + # Hook Script API base methods + # + + def srcdir_root + @srcdir + end + + def objdir_root + @objdir + end + + def relpath + @currdir + end + + # + # Config Access + # + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + + def verbose_off + begin + save, @config.verbose = @config.verbose?, false + yield + ensure + @config.verbose = save + end + end + + # + # TASK config + # + + def exec_config + exec_task_traverse 'config' + end + + alias config_dir_bin noop + alias config_dir_lib noop + + def config_dir_ext(rel) + extconf if extdir?(curr_srcdir()) + end + + alias config_dir_data noop + alias config_dir_conf noop + alias config_dir_man noop + + def extconf + ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt + end + + # + # TASK setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin(rel) + files_of(curr_srcdir()).each do |fname| + update_shebang_line "#{curr_srcdir()}/#{fname}" + end + end + + alias setup_dir_lib noop + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + alias setup_dir_data noop + alias setup_dir_conf noop + alias setup_dir_man noop + + def update_shebang_line(path) + return if no_harm? + return if config('shebang') == 'never' + old = Shebang.load(path) + if old + $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 + new = new_shebang(old) + return if new.to_s == old.to_s + else + return unless config('shebang') == 'all' + new = Shebang.new(config('rubypath')) + end + $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? + open_atomic_writer(path) {|output| + File.open(path, 'rb') {|f| + f.gets if old # discard + output.puts new.to_s + output.print f.read + } + } + end + + def new_shebang(old) + if /\Aruby/ =~ File.basename(old.cmd) + Shebang.new(config('rubypath'), old.args) + elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' + Shebang.new(config('rubypath'), old.args[1..-1]) + else + return old unless config('shebang') == 'all' + Shebang.new(config('rubypath')) + end + end + + def open_atomic_writer(path, &block) + tmpfile = File.basename(path) + '.tmp' + begin + File.open(tmpfile, 'wb', &block) + File.rename tmpfile, File.basename(path) + ensure + File.unlink tmpfile if File.exist?(tmpfile) + end + end + + class Shebang + def Shebang.load(path) + line = nil + File.open(path) {|f| + line = f.gets + } + return nil unless /\A#!/ =~ line + parse(line) + end + + def Shebang.parse(line) + cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') + new(cmd, args) + end + + def initialize(cmd, args = []) + @cmd = cmd + @args = args + end + + attr_reader :cmd + attr_reader :args + + def to_s + "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") + end + end + + # + # TASK install + # + + def exec_install + rm_f 'InstalledFiles' + exec_task_traverse 'install' + end + + def install_dir_bin(rel) + install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 + end + + def install_dir_lib(rel) + install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 + end + + def install_dir_ext(rel) + return unless extdir?(curr_srcdir()) + install_files rubyextentions('.'), + "#{config('sodir')}/#{File.dirname(rel)}", + 0555 + end + + def install_dir_data(rel) + install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 + end + + def install_dir_conf(rel) + # FIXME: should not remove current config files + # (rename previous file to .old/.org) + install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 + end + + def install_dir_man(rel) + install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 + end + + def install_files(list, dest, mode) + mkdir_p dest, @config.install_prefix + list.each do |fname| + install fname, dest, mode, @config.install_prefix + end + end + + def libfiles + glob_reject(%w(*.y *.output), targetfiles()) + end + + def rubyextentions(dir) + ents = glob_select("*.#{@config.dllext}", targetfiles()) + if ents.empty? + setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" + end + ents + end + + def targetfiles + mapdir(existfiles() - hookfiles()) + end + + def mapdir(ents) + ents.map {|ent| + if File.exist?(ent) + then ent # objdir + else "#{curr_srcdir()}/#{ent}" # srcdir + end + } + end + + # picked up many entries from cvs-1.11.1/src/ignore.c + JUNK_FILES = %w( + core RCSLOG tags TAGS .make.state + .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb + *~ *.old *.bak *.BAK *.orig *.rej _$* *$ + + *.org *.in .* + ) + + def existfiles + glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) + end + + def hookfiles + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| + %w( config setup install clean ).map {|t| sprintf(fmt, t) } + }.flatten + end + + def glob_select(pat, ents) + re = globs2re([pat]) + ents.select {|ent| re =~ ent } + end + + def glob_reject(pats, ents) + re = globs2re(pats) + ents.reject {|ent| re =~ ent } + end + + GLOB2REGEX = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + + def globs2re(pats) + /\A(?:#{ + pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') + })\z/ + end + + # + # TASK test + # + + TESTDIR = 'test' + + def exec_test + unless File.directory?('test') + $stderr.puts 'no test in this package' if verbose? + return + end + $stderr.puts 'Running tests...' if verbose? + begin + require 'test/unit' + rescue LoadError + setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' + end + runner = Test::Unit::AutoRunner.new(true) + runner.to_run << TESTDIR + runner.run + end + + # + # TASK clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias clean_dir_bin noop + alias clean_dir_lib noop + alias clean_dir_data noop + alias clean_dir_conf noop + alias clean_dir_man noop + + def clean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'clean' if File.file?('Makefile') + end + + # + # TASK distclean + # + + def exec_distclean + exec_task_traverse 'distclean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias distclean_dir_bin noop + alias distclean_dir_lib noop + + def distclean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'distclean' if File.file?('Makefile') + end + + alias distclean_dir_data noop + alias distclean_dir_conf noop + alias distclean_dir_man noop + + # + # Traversing + # + + def exec_task_traverse(task) + run_hook "pre-#{task}" + FILETYPES.each do |type| + if type == 'ext' and config('without-ext') == 'yes' + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, "#{task}_dir_#{type}" + end + run_hook "post-#{task}" + end + + def traverse(task, rel, mid) + dive_into(rel) { + run_hook "pre-#{task}" + __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') + directories_of(curr_srcdir()).each do |d| + traverse task, "#{rel}/#{d}", mid + end + run_hook "post-#{task}" + } + end + + def dive_into(rel) + return unless File.dir?("#{@srcdir}/#{rel}") + + dir = File.basename(rel) + Dir.mkdir dir unless File.dir?(dir) + prevdir = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir prevdir + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + + def run_hook(id) + path = [ "#{curr_srcdir()}/#{id}", + "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } + return unless path + begin + instance_eval File.read(path), path, 1 + rescue + raise if $DEBUG + setup_rb_error "hook #{path} failed:\n" + $!.message + end + end + +end # class Installer + + +class SetupError < StandardError; end + +def setup_rb_error(msg) + raise SetupError, msg +end + +if $0 == __FILE__ + begin + ToplevelInstaller.invoke + rescue SetupError + raise if $DEBUG + $stderr.puts $!.message + $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." + exit 1 + end +end diff --git a/test/samples/addressbook.csv b/test/samples/addressbook.csv new file mode 100644 index 00000000..3de3b479 --- /dev/null +++ b/test/samples/addressbook.csv @@ -0,0 +1,6 @@ +id,name,phone,street,town,state +1,Inky,555-000-1234,Druary Lane,Union City,CT +2,Blinky,525-0529-123,Apple Street,Robot Town,NJ +3,Clyde,247-219-4820,Sandbox Hill,Alvin's Landing,PA +4,Pacman,283-102-8293,Rat Avenue,Southford,VT +5,Mrs. Pacman,214-892-1892,Conch Walk,New York,NY diff --git a/test/samples/data.csv b/test/samples/data.csv new file mode 100644 index 00000000..91c0d19b --- /dev/null +++ b/test/samples/data.csv @@ -0,0 +1,3 @@ +"col1","col2","col3" +"a","b","c" +"d",,"e" diff --git a/test/samples/document.xml b/test/samples/document.xml new file mode 100644 index 00000000..5f14b600 --- /dev/null +++ b/test/samples/document.xml @@ -0,0 +1,22 @@ + + + +
+ Table of Contents +
+
+ How to install Ruport + How to use Ruport + How to rock and roll +
+
+ +
+
+ +
+ + +
+ + diff --git a/test/samples/ruport_test.sql b/test/samples/ruport_test.sql new file mode 100644 index 00000000..5ee372f3 --- /dev/null +++ b/test/samples/ruport_test.sql @@ -0,0 +1,8 @@ +create table ruport_test ( + a varchar(55), + b varchar(55), + c varchar(55), + d varchar(55) +); +insert into ruport_test values( 'a column, row 1', 'b column, row 1', 'c column, row 1', 'd column, row 1' ); +SELECT * FROM ruport_test; diff --git a/test/samples/stonecodeblog.sql b/test/samples/stonecodeblog.sql new file mode 100644 index 00000000..5882a448 --- /dev/null +++ b/test/samples/stonecodeblog.sql @@ -0,0 +1,279 @@ +-- MySQL dump 10.9 +-- +-- Host: localhost Database: stonecodeblog +-- ------------------------------------------------------ +-- Server version 4.0.17-standard +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Current Database: `stonecodeblog` +-- + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `stonecodeblog`; + +USE `stonecodeblog`; + +-- +-- Table structure for table `wp_categories` +-- + +DROP TABLE IF EXISTS `wp_categories`; +CREATE TABLE `wp_categories` ( + `cat_ID` bigint(20) NOT NULL auto_increment, + `cat_name` varchar(55) NOT NULL default '', + `category_nicename` varchar(200) NOT NULL default '', + `category_description` longtext NOT NULL, + `category_parent` int(4) NOT NULL default '0', + PRIMARY KEY (`cat_ID`), + KEY `category_nicename` (`category_nicename`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `wp_categories` +-- + + +/*!40000 ALTER TABLE `wp_categories` DISABLE KEYS */; +LOCK TABLES `wp_categories` WRITE; +INSERT INTO `wp_categories` VALUES (1,'Tutorials','tutorials','This is a collection of tutorial like articles\r\n',0),(2,'General','general','This is general information',0),(3,'Charity','charity','These posts have to do with charitable donations sponsored by or provided by Stone Code',0),(4,'Projects','projects','This is information regarding Stone Code projects',0),(5,'Reviews','reviews','',0),(6,'Rants','rants','',0),(7,'Company','company','',0),(8,'Humor','humor','',0),(9,'RubyConf 2005','rubyconf-2005','',0),(10,'Ruby Groups','ruby','',0); +UNLOCK TABLES; +/*!40000 ALTER TABLE `wp_categories` ENABLE KEYS */; + +-- +-- Table structure for table `wp_comments` +-- + +DROP TABLE IF EXISTS `wp_comments`; +CREATE TABLE `wp_comments` ( + `comment_ID` bigint(20) unsigned NOT NULL auto_increment, + `comment_post_ID` int(11) NOT NULL default '0', + `comment_author` tinytext NOT NULL, + `comment_author_email` varchar(100) NOT NULL default '', + `comment_author_url` varchar(200) NOT NULL default '', + `comment_author_IP` varchar(100) NOT NULL default '', + `comment_date` datetime NOT NULL default '0000-00-00 00:00:00', + `comment_date_gmt` datetime NOT NULL default '0000-00-00 00:00:00', + `comment_content` text NOT NULL, + `comment_karma` int(11) NOT NULL default '0', + `comment_approved` enum('0','1','spam') NOT NULL default '1', + `comment_agent` varchar(255) NOT NULL default '', + `comment_type` varchar(20) NOT NULL default '', + `comment_parent` int(11) NOT NULL default '0', + `user_id` int(11) NOT NULL default '0', + PRIMARY KEY (`comment_ID`), + KEY `comment_approved` (`comment_approved`), + KEY `comment_post_ID` (`comment_post_ID`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `wp_comments` +-- + + +/*!40000 ALTER TABLE `wp_comments` DISABLE KEYS */; +LOCK TABLES `wp_comments` WRITE; +INSERT INTO `wp_comments` VALUES (4,3,'Jordan','JByro1@newhaven.edu','','64.252.35.243','2005-08-21 09:16:55','2005-08-21 13:16:55','Very sexy indeed. Again Bob and I would like to thank you for letting us mooch the space for a while. Even when we move over to UNH\'s servers, we will always provide a link back to where ever you home on the internet may be for all your help.',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8b4) Gecko/20050718 Camino/0.9a2','',0,0),(6,9,'Jordan','JByro1@newhaven.edu','','192.132.64.2','2005-09-12 15:20:25','2005-09-12 19:20:25','I just used this in my C Lab and was out of there in 15 minutes. It took me longer to login then to do the lab. Yay for Intro to C!',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8b4) Gecko/20050718 Camino/0.9a2','',0,0),(7,12,'Nick','NZura1@newhaven.edu','','69.37.10.184','2005-09-20 17:04:05','2005-09-20 21:04:05','Wow that was great. While I did indeed have no clue about what the \"Network Status\" email said, you explained it in such a simple way that I understand now. Even though I am a commuter student and this really had no effect on me, I think that it is great that you stood up for yourself and the rest of the University in order to get things fixed that are much more important then some of the other things going on there (e.g. planthing tons of new bushes and shrubs). I support your effort and appreciate you telling things like they really are. Thanks',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; Media Center PC 3.1)','',0,0),(8,12,'Ryan','rluck2001@yahoo.com','','66.30.123.186','2005-09-20 17:09:09','2005-09-20 21:09:09','WOW. You lost the internet for two hours and you couldn\'t play dungens and dragons online. Next time maybe you should just go outside and enjoy the day rather then spending time and filling up everyone email with your sob story of how you miss the internet. At least the technology department fixes issues in a timely mannor...just wait for the housing lottery or course selection...',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)','',0,0),(9,12,'Jordan','JByro1@gmail.com','http://jordan.stonecode.org','192.132.64.2','2005-09-20 17:20:20','2005-09-20 21:20:20','Ryan. Congratulations, most of the time I do not waste my time responding to \"Simple\" people like you, but for some reason I too what you said about Greg and what he is doing in the wrong aspect. \r\n\r\nI will agree, to a close-minded person such as yourself, that is all you see the Internet being used for. Some people, myself included, use the Internet for work. I am fortunate enough to be able to work remotely from school using the university’s Internet connection. Lucky for me, I chose to go into the office this weekend and do some onsite administrative tasks. Had I not, I would have been unable to perform my job.\r\n\r\nAlso, perhaps you were not around on the weekend when the entire network went on the fritz, but the weather was terrible. I would love to know how you enjoyed a nice rainy day outside. \r\n\r\nMy last point; this email that Greg sent out was one of the only emails sent by a student that I found relevant to EVERYONE. Sure, some people may like cars, and some people may want to play in the band, but everyone who checks their mail uses the Internet. So if there is anyone you should be complaining about, it is not Greg and his “Crusadeâ€Â.\r\n\r\n- Jordan Byron\r\nunhmb.stonecode.org',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8b4) Gecko/20050914 Camino/1.0a1','',0,0),(10,12,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','65.75.17.113','2005-09-20 17:42:38','2005-09-20 21:42:38','Thank you for your comments.\r\n\r\nFor the individual who accused me of perpetual D&D playing, you clearly did not read my email. The 2 hours lost prevented me from running a meeting with several commuters as well as students on or off campus for those interested in technology here on campus and around the area. This literally made the meeting (which I was supposed to run) impossible.\r\n\r\nAnd actually, I was outdoors for the duration of the time the outage happened, because I quite enjoy being outside whenever I can. But that doesn\'t mean I shouldn\'t be able to educate people while doing so!\r\n\r\nAnd for me, my work resides on the computer. As far as housing lottery or course selection go, I\'m aware this is a nightmare for some, but honestly, I\'ve had no friction in it.\r\n\r\nBut nevertheless, I welcome your criticism. Maybe next week I\'ll tell the 13 members of my group that we can\'t have a meeting because I\'m out picking flowers or something.',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.8b4) Gecko/20050908 Firefox/1.4','',0,2),(11,12,'Alana','AAndr1@newhaven.edu','','68.63.96.216','2005-09-20 18:13:46','2005-09-20 22:13:46','While I appreciate your concerns, I do not appreciate the e-mails you are sending out. The UNH e-mail system is not really intended for people to voice their opinions on various issues. The system is set up for organizations and faculty to get important information to students. Some people choose to use it for other purposes, such as trying to sell their car, or in your case, to complain. All this does is irritate people and discourage them from using the UNH e-mail system. Please do not continue to send mass e-mails because you are angry about something.',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; MSOCD; AtHome020; SV1; .NET CLR 1.1.4322)','',0,0),(12,12,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','65.75.17.113','2005-09-20 18:24:26','2005-09-20 22:24:26','In the beginning of the semester, we were told to use it to contact other students and make announcements. (Such as my announcement regarding why my meeting for UNH Students was canceled which was the primary point of that message). I realize that my initial email was harsh but it served it\'s purpose in generating awareness among students. I hardly consider this an abuse of the system, however, I do try to keep my usage of it to a minimum, moving public discussion here for instance.\r\n\r\nThe fact that there was more to the story than the tech department let us know I considered important information which should be distributed to the student body. You may disagree, however I know that a sizeable portion of the community was appreciative of my efforts, due to the 5 emails, 7 instant messages, and two positive comments on the board.\r\n\r\nIf 14 people were positively effected by my message, I see it is a perfectly reasonable use of a system that is designed for such a use. If it were not, students would not have access to the aggregators.\r\n\r\nI do wholeheartedly agree that selling cars and conducting other personal business should be done elsewhere. But my message addressed two relevant things, A campus group that was supposed to meet but did not, and an announcement that did not completely explain the situation.\r\n\r\nFor you to say this isn\'t fair use of the system is not justified as far as I see it. After all, we all do have that wonderful \'delete\' key at our disposal.',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.8b4) Gecko/20050908 Firefox/1.4','',0,2),(13,12,'Bobby','rbove1@newhaven.edu','','192.132.64.2','2005-09-20 18:45:05','2005-09-20 22:45:05','seriously get a life man...',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.10) Gecko/20050716 Firefox/1.0.6 (ax)','',0,0),(14,12,'Jordan','JByro1@gmail.com','http://jordan.stonecode.org','192.132.64.2','2005-09-20 19:05:03','2005-09-20 23:05:03','Yay! Lets degrade the discussion to personal attacks and useless slander! It is amazing to see this type of behavior among students from the same University I attend. It really makes me proud of the high standards this school sets for incoming and current students.\r\n\r\nI’ve always looked away and said, “this doesn’t happen muchâ€Â, whenever I see people like Bobby on campus. The fact is that there is a minority of “Bobbies†on campus. Without getting totally off topic, I would like to challenge those of us who don’t subscribe to the “Bobby†pathology. Rise above their antics and calmly remind them that high school is over, and the time to “grow up†has long past.\r\n\r\nI am still disheartened by those of us who don’t have the common curtsey to be aware of others around them. We live, sleep, eat, and learn together; be aware of that!\r\n\r\nI am proud of UNH, and in the time I have been here, I can see it going somewhere. The freshman these years are very ambitious, and it seems like soon we will have a club for just about everything. \r\n\r\nI will admit, it is a little annoying to see emails about clubs that I have no interest in, but I keep an open mind. I really don’t care what the pastry-baking club is doing next Thursday. I do however acknowledge the power of UNH email, and how it can be used to access students never before available.\r\n\r\nSo please, keep open minds, and just use the Delete Key!!!!',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8b4) Gecko/20050914 Camino/1.0a1','',0,0),(15,12,'Bill','Wdund1@newhaven.edu','','192.132.64.2','2005-09-20 19:15:31','2005-09-20 23:15:31','I am not what you consider a huge techie, do i code no, can I repair them yeah, And the Internet in this Place is Appualing. To most the Internet is just Iming and E-mail and people just can\'t live without that, but you got a point there are actually some of us who actually use the internet for other useful things. And I\'ll be honest people call it a \"cursade\" I call it a wake up call, anytime I have gone to tech services, to see what they say any time my perfectly working PC goes on the blink when I go on their netowrk and see what they say and when I leave my head hurts. From asking why are they thinking that and two voicing a complaint whith a service organition on campus is 1 freedom of speech, as was noted we have a delete key and two we pay to come here they help us if we voice enough difaction with the current people, new competent people maybe hired. That\'s all I am saying.',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)','',0,0),(16,12,'Sean','seanwurster@hotmail.com','','68.63.139.164','2005-09-20 19:35:02','2005-09-20 23:35:02','Maybe we will have to go back to the old days when we actually had to meet at the library or talk to a real person when a group had to meet or a discussion had to take place...oh the horror! Don\'t become too dependent on the internet learn to adapt for Pete\'s sake!',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)','',0,0),(17,12,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','65.75.17.113','2005-09-20 19:38:00','2005-09-20 23:38:00','Our group consists of experts from out of state and people who cannot readily get to campus. However, we do have weekly on campus meetings, albiet without the members who can only realistically telecommute.\r\n\r\nThe online meetings are intended to suppliment our on campus meetings and allow more people to be involved. Since it\'s a programming group, the internet does play a giant role in what we do.\r\n\r\nPlease at least acquaint yourself with the situation before you pass judgement.',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.8b4) Gecko/20050908 Firefox/1.4','',0,2),(18,12,'Rob','rcani1@newhaven.edu','','65.75.17.113','2005-09-20 19:40:36','2005-09-20 23:40:36','Before I start, I have one thing to say to everyone: to each his own. I don\'t believe in bashing people just because of the things they do or don\'t become involved in. Even if you believe that someone is over-indulgent in something, who are you to judge that person?\r\n\r\nPutting that aside, because it seems that this blog was meant to discuss technical issue at UNH and its effect on the student body, I believe that the mass email to the student body is justified. This problem goes so much farther than two hours of non-existent internet service; I agree whole-heartedly that there are serious problems with the network that need to be addressed. I\'ll admit that i don\'t entirely understand all of the problems with the system. I\'ll be frank; i don\'t understand them at all. But I believe its the school\'s responsibility to hire someone who does understand the problem, and the students\' responsibility to recognize problems and bring it to light. \r\n\r\nEven thought the internet crash didn\'t affect me, as I am a commuter student that was sleeping at home during the crash, I am relieved to seey students who are actually taking part in the community and expresses their opinions on this topic.',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.7) Gecko/20050414 Firefox/1.0.3','',0,0),(19,12,'Allen','wtf9862@yahoo.com','http://allen.stonecode.org','69.0.120.222','2005-09-20 19:48:50','2005-09-20 23:48:50','While a minority of students have decided to use this forum as a place to spread their antipathy towards Greg, I would like to take this time to commend him for his efforts thus far.\r\n\r\nNot only did Greg show the power of the student body at UNH, he unraveled the esoteric language chosen by the Tech Deptartment, and shared this information with the UNH community. \r\n\r\nWhile I am a commuter, and was not effected by the lack of a network, I know how detrimental loss of Internet access is for those of us who utilize the Internet for business and programming purposes. As a writer who uses the Internet as his chief form of communication with employers and potential employers, the inability to perform work is akin to not showing up to a job. In short, it makes you seem unreliable. While I realize some of you can not grasp that concept. it is not a matter of playing games or not having the Internet for the Internet\'s sake, it is about losing money, and business. In Greg\'s case it is a matter of being unable to partake in a project that benefits a certain technical community.',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7) Gecko/20040616','',0,0),(21,12,'Erica','ECove1@newhaven.edu','','192.132.64.2','2005-09-20 22:05:53','2005-09-21 02:05:53','Hello\r\n\r\nI just wanted to let you know as Senator in USGA to come to meetings to vent your frustrations and issues. Also, the library and student center had internet and that is always an option. I know its frustrating because I had to do my work in the library but the IT staff has been working extra hard to fix the issues that was out of their control.',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; AT&T WNS5.0; SV1)','',0,0),(23,12,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-09-20 22:21:36','2005-09-21 02:21:36',' Though I appreciate the suggestion, due to various reasons, my group is not registered with the USGA nor do I intend to do so. As an individual, I prefer the direct route to the source of problems, which is why I contacted Alan and Greg. They provided me with sufficient information to form this article.\r\n\r\n The Library, Student Center, Engineering Labs, and CS Labs were all without internet. The entire network was down from 9:00pm to 11:00pm sunday.\r\n\r\nI\'ve spoken to the IT staff. There was a SCHEDULING issue that prevented this from being handled timely, and if you read through the article you just commented on, you would have seen that.\r\n\r\nAs a side note to the individuals who have abused this forum with racist or obscene remarks, auditing software is installed on this system to prevent abuse, and I will report any further abuse I find.\r\n\r\nOtherwise, I encourage everyone elses comments, even if they are criticism or suggestions.',0,'1','Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050521 Firefox/1.0.4','',0,2),(25,12,'Diane','dbuno1@newhaven.edu','','64.252.56.88','2005-09-21 08:00:53','2005-09-21 12:00:53','\r\nPersonally, coming from a state university where apathy towards everything is commonplace I find it refreshing to see that someone actually has an opinion about something and doesn\'t mind taking action. My own personal view is that too many people find it an easier undertaking to sit idly by while the rest of the world makes decisions that directly effect each of us and then quietly complain/or not about the end result. \r\nI think there is a much larger issue here than is being addressed. First, it is everyone\'s right, especially in this collegiate setting, to have an opinion and rights and to be able to freely voice/defend both. If there were more people like Greg who were passionate about something perhaps the changes that everyone sought would happen in a more timely fashion. From my own personal viewpoint, students have become so reliant upon others making decisions for them that they blindly accept anything that is spoon-fed to them and don\'t question anything. I\'ve seen more people agree with nonsense or take it as \"fact\" when in actuality what they were being told was simply not logical or even remotely feasible. \r\nNow on to my next point. Again, as a student coming from a state university outages were quite commonplace and the only way a student could find out what the problem was we had to contact the university ourselves. Ever try to get an actual human on the phone in a state university? It\'s like trying to ambidextrous violin player in a crack house. In light of that fact, I did think that while the email from the Techno-geek staff was mildly confusing I did however, appreciate that they sent out even the smallest tidbit of an explanation. I thought that in itself was the responsible thing to do.\r\nIn closing I will add that to personally attack people in a sophomoric fashion for having an opinion is not only embarrassing to the author, but is not representative of the intellectual integrity of the student population at UNH. For those who think otherwise, have a beer and turn on Southpark - they\'re always on.',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)','',0,0),(26,12,'Sam','samilionester@yahoo.com','','192.132.64.2','2005-09-21 15:34:55','2005-09-21 19:34:55','Why hasn\'t anyone asked the obvious question related to this situation? The University never has enough money to do this, or that, or the other. This is always the excuse I hear. So why is this the case? Is it not a known fact that the University earns upwards of around 30,000 per student per year?! At the school I attended before this one I was paying around 2,000 per year, and I never had any complaints as far as facilities, networks, parking, etc. etc. etc. go. At the school I attended before this one, the computer lab was in a huge beautiful building with state of the art computers, rather than in a trailer with half-par computers. Also, every classroom was a \"smart classroom\" and this was back in 2002 and 2003, and that was not to mention that the entire campus had wi-fi access. \r\n\r\nDon’t get me wrong I have noticed the improvements that have been made, and I have been happy with them, but nevertheless, if I\'m paying 15 times the amount I was paying at my former school, shouldn\'t I be getting 15 times the education, 15 times the facilities, 15 times the parking, and 15 times the network reliability?!\r\n\r\nAgain, don\'t get me wrong, I do like this University a lot. I must say, the professors make all the difference, but I can\'t help but ask the question, where is our money going? We aren\'t paying much less than Yale students pay, but are we getting even near what Yale students get?',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)','',0,0),(27,12,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-09-21 16:31:49','2005-09-21 20:31:49','Yeah, the \"bush and tree\" fund is something many people, including myself are curious about. It seems as if the priorities of the University are placed slightly more on the first impression of students rather than the lasting impression of the current students. It\'s understandable that this is important to some extent, but it doesn\'t mean they should neglect critical services to do so. From the rumor mill, I\'ve heard that the technology department\'s funding has not been lowered in exchange for campus beautification, but the fact that it hasn\'t increased along with the increased need is troubling to me. The bottom line is, if the school wants to experience growth, they can\'t just slap up a few extra residence halls and leave out the tremendous additional support structure necessary. It\'s a vicious cycle, but hopefully things like this will help bring light to the issue.\r\n\r\nMy question to you about your former school is whether it was a public school. If it was, my answer to your expectations is a simple \"no\" because the schools are funded substantially by the government while private institutions for lack of a better term need to pimp themselves to rich alumni to get most of their funds. However, if it was a private school, we should be on the phone with them figuring out how the hell they fund themselves so well!\r\n\r\nMy feeling at UNH is that it\'s important to get students to come here, but even more important to keep them, and to do that, we need to take care of what really matters first, before we put a nice icing on the cake.',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.10) Gecko/20050716 Firefox/1.0.6','',0,2),(28,12,'Kevin Laroche','Klaro1@newhaven.edu','','192.132.64.2','2005-09-21 16:42:17','2005-09-21 20:42:17','Hey greg, I can assure that Greg Bartholemew is doing everything in his power to keep the network running smooth. Unlike most of the people that comment or even those who start topics like this, I actually work in Unh It dept. I see things on both sides and i can assure you that you are being rediculous. It is understandable to be upset for not having an active internet connection but there\'s no need to go pointing fingers and that\'s excatly what you\'ve done. Greg B has a job to do and he does it well in my opinion. If you want to point your finger you can point it at Extreme Networks b/c that the hardware that failed. Next time you should be considerate before you get unruly and have an apparent breakdown. Let me know what you think. My ext is 7306 \r\nThanks, \r\nKevin \r\n\r\nps. Have a nice day on the interent since now it has been fixedd',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5','',0,0),(29,12,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-09-21 17:06:33','2005-09-21 21:06:33','I am thankful that Greg and Alan were more professional than you were. The finger I point is only at whoever mismanaged scheduling which caused the excessive delay. Greg recognized the points that I had to make about student relations, and I can tell by his emails to the campus that he put some effort into being more sensitive to student needs than he did before this chaos ensued. If you read through the entire post above, I never accused Greg or Alan of not working hard. As far as I\'m concerned, I am happy that the new part is in place and that the opinions of the student body have been voiced, and that it certainly seems that the technology department has listened. Now, if only the university would address issues on funding, this situation would be ideal.\r\n\r\nBy calling me ridiculous and clearly not fully reading my article, you certainly do not reflect your department well. However, I must reiterate that both Alan and Greg were as professional as I could expect two individuals in high positions under pressure could be.\r\n\r\nAnd for those who assume that this rant was without dialogue between myself and the IT department (which is what you suggest), that is simply not true.\r\n\r\nI talked to both Alan and Greg and got answers to all of my questions before I stated my opinion on this weblog.\r\n\r\nThat having been said, the commotion this has caused is certainly overwhelming. I\'d be happy simply to let it rest at this point with the lessons learned by myself, the student body, the technology department, and the university preserved.',0,'1','Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050521 Firefox/1.0.4','',0,2),(30,12,'Kevin Laroche','Klaro1@newhaven.edu','','192.132.64.2','2005-09-21 17:11:59','2005-09-21 21:11:59','Just to clarify I\'m a work study student and My reply to your blog doesn\'t represent the It dept in anyway.',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5','',0,0),(31,12,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-09-21 17:16:49','2005-09-21 21:16:49','I made that assumption by your comments, but I\'m thankful that you cleared that up. As far as anyone who is concerned about my experience or credentials, I am a COMPTIA A+ Core/OS and Network+ Certified Professional. I\'ve worked as the night administrator/technician on a network with over 1600 machines, and am currently a freelance programmer under the employ of B-Tree Technologies. So it\'s not like I just said, \"y0 d00d my 1nt3rn3t izn\'t w0rkingz\" \r\n\r\nThat having been said, there are more interesting problems to confront at this university, and it appears like the majority of this one has been solved, so lets let my poor webserver have a rest, unless you have something new and interesting to say.\r\n\r\nI do appreciate everyone who left constructive comments on this blog, whether or not they were in agreement with my point of view.\r\n',0,'1','Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050521 Firefox/1.0.4','',0,2),(32,12,'Pete','unh@charter.net','','24.6.190.33','2005-09-21 18:20:51','2005-09-21 22:20:51','Greg! Keep up the good work!',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.7) Gecko/20050414 Firefox/1.0.3','',0,0),(33,12,'Ryan','rkeri1@newhaven.edu','','192.132.64.2','2005-09-22 12:30:53','2005-09-22 16:30:53','Ok, I understand that the network went down for a long time over the weekend. I am a resident who stays on campus. I was a little sad that it went down and a little frustrated. However i got a lot of homework done.\r\n\r\nA few points of note. We aren\'t a large state university like UConn or PennState, therefore, we do not get state funding for technology like UConn or PennState does. Tuition at private universities like this one and University of Hartford is always higher than State Universities. We pay a lot of money, and banks pay the rest. However i do not thnk that because a lot of money is paid on our behalf(i personally only know of one person who doesn\'t get any financial aid) I don\'t think that gives us a \"right\" to internet access. I see it more as a privilage, such as driving a car.\r\n\r\nI also see the internet becoming more and more like the \"Easy\" button you see on the Staples commercials. It\'s very easy to host online meetings instead of local meetings. More people can attend who wouldnt be able to otherwise. You can talk to all your friends at once and get help with homework you are having trouble with without having to run all over campus. But i think too many of us rely too much on the internet and when our \"Easy\" button breaks we panic and aren\'t sure what to do.\r\n\r\nAlso, i was told that working for anyone outside the university from my dorm using the internet was prohibited as well as setting up a business from within my dorm and using the internet connection to promote it was also prohibited.\r\n\r\nGreg, i know your group isn\'t registered with the USGA and i don\'t blame you, there is a lot of administrative paperwork that needs to be done and its only worth it if you are a larger club on campus. But that doesn\'t mean you aren\'t allowed to go to the USGA and let them know that you think what happened was rediculous and should be fixed. You should go and stand up for your rights, if that is what you truely wish to do. Go the USGA and tell them that you want more school funds diverted to maintaining our network and reducing downtime.\r\n\r\nI\'ve had problems with the campus network myself before and including this latest time, but i\'ve emailed Greg B and Alan personally without including the entire student body, and i think that is where a lot of angry with you. You mentioned that the person who works for the IT department that responded here was unprofessional in their responce to you, but i feel that you were unprofessional including the entire student body in what appears to be a rant about our technology department.',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; WOW64; .NET CLR 1.1.4322)','',0,0),(34,12,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-09-22 13:32:46','2005-09-22 17:32:46','My first email to the student body (which is what I suspect caused this outrage) was certainly uncalled for and overly biased. However, the account in this article does have inforrmation that is of value to the student body which they would not otherwise have been exposed to. You\'re certainly right when you say that by venting my frustrations to the student body and pointing the finger at the technology department was unfair. I should have emailed them privately, collected the information, and then passed only the second email that led people here. I do think that the information in this article (along with the voice of the student body) holds incredible value that cannot be found in direct one on one discourse or with the USGA. My means to get here however, were indeed unprofessional, and were the result of frustration and poor judgement.\r\n\r\nIt seems as if this thread has become more of a discussion about the USGA than it is about the Network outage. I will post a comment with a link to a thread regarding the USGA when I get a chance to start one, because I would like to voice my opinions regarding the matter and moreso (because I know little about the organization) request the opinions of others on the matter.\r\n\r\nHowever, until then, let\'s refrain from discussing the effectiveness of the USGA on this article\'s thread, as it is certainly off topic.\r\n\r\nThank you for your insightful response.',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0','',0,2),(35,16,'Jordan','JByro1@gmail.com','http://jordan.stonecode.org','69.177.127.78','2005-10-04 12:33:49','2005-10-04 16:33:49','How did you get those reports? Is that something I can run for UNHMB??? We currently use extremetracking.com, and it works, but if I can use a native service and remove that unsightly link from our main page, Iwould prefer it!\r\n\r\nVery cool to see Firefox in the lead, and Macs in a close second in OSs',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7','',0,0),(36,16,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-10-04 13:07:37','2005-10-04 17:07:37','I can add you as a user, but I\'m not sure if I can filter it by subdomain :-/',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0','',0,2),(37,16,'Jordan','JByro1@gmail.com','http://jordan.stonecode.org','69.177.127.78','2005-10-04 13:18:44','2005-10-04 17:18:44','Ah that is all right. It just looked nice. But thanks anyway!',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7','',0,0),(38,20,'Pete Johanson','crazzypeat@yahoo.com','','69.37.151.75','2005-10-13 21:03:53','2005-10-14 01:03:53','Hey man im finaly checking your page out and its pretty nifty. Im just wondering how you are doing with all of the travel? I mean you are in safe hands right? I dont want anything to happen to you! Hey and if you ever make it to mexico have some tequila (or however you spell it) for me! Oh and eat the worm too ;) lol just joshing. So anyways get back to me ASAHP (as soon as humanly possible). I miss you! We all miss you! xoxoxoxoxoxoxoxoxoxoxoxoxo I LOVE YOU!!!!!!!!!',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.8) Gecko/20050511 Firefox/1.0.4','',0,0),(39,27,'Jim Weirich','jim@weirichhouse.org','http://onestepback.org','65.88.180.234','2005-10-17 09:14:24','2005-10-17 13:14:24','You can find the source code to the Rubic\'s cube demo at http://onestepback.org/articles/lingo/rubiks/index.html',0,'1','Mozilla/5.0 (X11; U; Linux i686; rv:1.7.3) Gecko/20040914 Firefox/0.10','',0,0),(40,31,'Gary Blomquist','garyblomquist@yahoo.com','','64.139.74.96','2005-11-09 09:28:50','2005-11-09 13:28:50','Is there a mailing list or web site for the group?',0,'1','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)','',0,0),(41,31,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-11-09 10:04:04','2005-11-09 14:04:04','NYC.rb has a yahoo group:\r\nhttp://groups.yahoo.com/group/ruby-nyc/\r\n\r\nour group here in CT has a google group:\r\nhttp://groups.google.com/group/New-Haven-Ruby-Brigade?lnk=li\r\n\r\nand a wiki:\r\nhttp://newhavenrubyists.org',0,'1','Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050521 Firefox/1.0.4','',0,2),(42,33,'Aslak Hellesoy','aslak.hellesoy@gmail.com','http://aslakhellesoy.com','80.111.11.252','2005-11-09 11:00:46','2005-11-09 15:00:46','These are not mocks, but stubs (aka fakes)\r\n\r\nhttp://www.jmock.org/oopsla2004.pdf\r\nhttp://www.martinfowler.com/articles/mocksArentStubs.html\r\n\r\nAslak',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.7.8) Gecko/20050511 Firefox/1.0.4','',0,0),(43,33,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-11-09 11:07:52','2005-11-09 15:07:52','Damn. I wasn\'t exactly sure if they were mocks or stubs, but now I know :)\r\n\r\nSo this article should really be called \"Stop Stubbing Me!\"\r\n\r\nI even got the semantics right in MockReport by calling MockDB fakeDB....\r\n\r\nIn Ruport 0.3.0 you\'ll see FakeReport and FakeDB, to correct the nomenclature.\r\n\r\nUnless of course, you\'ve got ideas for some real mocks. :)\r\n\r\nThanks for pointing this out, and for the great resource.',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0','',0,2),(44,21,'dustin chapman','trevor@chello.nl','http://www.sun.com','196.7.0.160','2005-11-11 14:42:50','2005-11-11 18:42:50','you have some really cool stuff at your site. i\'m sure gonna come back here. think that will make relief: http://www.apple.com , think that will make relief , black girls on their mission',0,'spam','Mozilla/4.0 (compatible; MSIE 5.5; Windows 98)','',0,0),(45,7,'Daniel Baumann','Eric@yahoo.com','http://www.quotationspage.com/quotes/Lily_Tomlin','218.38.160.84','2005-11-11 14:43:29','2005-11-11 18:43:29','Great blog. It\'s nice to be here! Discontent makes rich men poor: http://www.bartleby.com/100/ , A good conscience is a continual Christmas , Few people are capable of expressing',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)','',0,0),(46,6,'Gabriel Drake','Samuel@discovery.com','http://www.quotationspage.com/quotes/Charles_Mackay','196.7.0.160','2005-11-11 14:44:16','2005-11-11 18:44:16','It was fun visiting here. Wishing you a great day! Content makes poor men rich: http://www.bartleby.com/100/ , which differ from the prejudices , although I am bringing a change of underwear',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)','',0,0),(47,3,'Richard Baker','Benjamin@internet.com','http://news.linux.com/news/05/10/10/0213220.shtml?tid=96','196.7.0.160','2005-11-11 15:15:09','2005-11-11 19:15:09','Very nice. I hope you\'ll update very soon. It\'s impossible to experience one\'s death: http://www.useful-information.info/quotations/life-quotes.html , it has been well said , The only thing you take with you',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)','',0,0),(51,35,'Jeremy Campbell','Dylan@gmail.com','http://www.sun.com','196.7.0.160','2005-11-13 03:03:06','2005-11-13 07:03:06','Cool site! I\'ll be back. my parents didnt told me about it: http://www.adobe.com , black girls on their mission , my parents didnt told me about it',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461)','',0,0),(50,33,'Zachary Bartrim','Jesse@gmail.com','http://www.adobe.com','196.7.0.160','2005-11-12 16:06:31','2005-11-12 20:06:31','Two thumbs up!!! black girls on their mission: http://www.panasonic.com , thins that excited you at 14 , think that will make relief',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1)','',0,0),(52,31,'joshua brown','michael@msn.com','http://www.yahoo.co.uk','196.7.0.160','2005-11-13 03:04:12','2005-11-13 07:04:12','cool stuff. keep up the good work. substances that cure you: http://www.sun.com , my parents didnt told me about it , my parents didnt told me about it',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)','',0,0),(53,9,'kevin allison','travis@gawab.com','http://www.pmai.org','196.7.0.160','2005-11-18 03:39:36','2005-11-18 07:39:36','you have some really cool stuff at your site. i\'m sure gonna come back here. while they only recover: http://www.worldlighthouses.org , Bad Opponents is always Greedy Boy while they only recover , Win TV is very good Girl life is the art of drawing sufficient conclusions',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)','',0,0),(54,7,'logan clark','joseph@internet.com','http://www.cr.nps.gov/maritime/handbook.htm','70.137.164.19','2005-11-18 03:39:53','2005-11-18 07:39:53','two thumbs up!!! most people are even incapable: http://www.ipl.org/div/light , Bad is feature of Coolblooded Cards i do not believe in an afterlife , when Slot is Girl it will Hope Soldier a good conscience is a continual christmas',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1)','',0,0),(55,6,'daniel adams','andrew@internet.com','http://lighthousing.net','219.93.174.105','2005-11-18 03:40:16','2005-11-18 07:40:16','you have some really cool stuff at your site. i\'m sure gonna come back here. if you can\'t pay for a thing, don\'t buy it: http://www.seathelights.com , Destroy Forecast Lose - that is all that Chips is capable of while they only recover , Green, Industrious, Small nothing comparative to Lazy it\'s the other lousy two percent',0,'spam','Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)','',0,0),(56,4,'richard chapman','cameron@chello.nl','http://www.worldlighthouses.org','211.56.248.38','2005-11-18 03:40:29','2005-11-18 07:40:29','good work. i like your site. the best condition in life is: http://psa-eid.org , right Cards will Steal Table without any questions only while the sun shines , when Mistery is Game it will Percieve Circle how linux thin-clients benefit schools',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)','',0,0),(57,13,'Jeremy Ford','Richard@gmail.com','http://www.cr.nps.gov/maritime/handbook.htm','219.93.174.107','2005-11-18 03:41:11','2005-11-18 07:41:11','Very original content. I really like your site. Discontent makes rich men poor: http://www.seathelights.com , Anticipate Corner is very good Cards Be at war with your vices , Cards will Mistery unconditionally It\'s impossible to experience one\'s death',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)','',0,0),(58,15,'evan ballard','kenneth@chello.nl','http://www.institutions.org.uk','196.7.0.160','2005-11-18 06:46:21','2005-11-18 10:46:21','you\'re doing a great work here. i enjoyed visiting here very much. thanks! black girls on their mission: http://www.crystalpalacefoundation.org.uk , Small, Collective, Bad nothing comparative to Central thins that excited you at 14 , Game will Chair unconditionally think that will make relief',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)','',0,0),(59,13,'sean moore','stephen@msn.com','http://www.cr.nps.gov/maritime/handbook.htm','200.21.18.135','2005-11-19 03:43:51','2005-11-19 07:43:51','it was fun visiting here. wishing you a great day! i do not believe in an afterlife: http://www.womeninphotography.org , Tremendous Round is always Coolblooded Cards while they only recover , Curious is feature of Red Opponents ninety-eight percent of the adults',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)','',0,0),(60,9,'jordan chapman','alex@gawab.com','http://www.lhdigest.com','200.21.18.135','2005-11-19 03:43:51','2005-11-19 07:43:51','you have some really cool stuff at your site. i\'m sure gonna come back here. i do not believe in an afterlife: http://www.womeninphotography.org , International Girl Compute or not hacking opensuse , Corner can Percieve Grass they go mad in herds',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)','',0,0),(64,40,'Greg','gregory.t.brown@gmail.com','http://stonecode.org','192.132.64.2','2005-12-08 08:52:51','2005-12-08 12:52:51','hmm.... sounds interesting.',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0','',0,2),(63,40,'Asger Ottar Alstrup','aalstrup@laerdal.dk','','85.82.1.195','2005-12-08 05:07:37','2005-12-08 09:07:37','What about JSON support?\r\nhttp://www.crockford.com/JSON/index.html\r\n\r\nJSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate.\r\n\r\nThere is a Ruby library for JSON:\r\nhttp://json.rubyforge.org/',0,'1','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8) Gecko/20051111 Firefox/1.5','',0,0),(65,46,'VovaBob','KuzaMom@yahoo.com','http://pd1.funnyhost.com','68.4.221.55','2005-12-25 21:59:36','2005-12-26 01:59:36','http://pd2.funnyhost.com\n desk3\n [url=http://pd4.funnyhost.com]desk4[/url]\n [link=http://pd6.funnyhost.com]desk6[/link]',0,'spam','Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)','',0,0),(66,47,'Simon Harris','simon@redhillconsulting.com.au','http://www.redhillconsulting.com.au/blogs/simon','220.238.180.238','2005-12-25 23:54:22','2005-12-26 03:54:22','I feel your pain. On Friday 23rd December I used my laptop to check my email @ home, put it to slee, went into work and attempted to awaken it but alas it was no more.\r\n\r\nSo I initiated plan A: rush it off to the \"hospital\". Unfortunatley the prognosis was not good--4 to 5 weeks.\r\n\r\nI then switched to plan B: emergency purchase of a 20\" G5 iMac.\r\n\r\nThanks to my handy contacts and calendar info on my iPod I was able to get back up and running in about 2 hours.\r\n\r\nAnd the world is good again. Though I will need to work my butt off to pay for it!',0,'1','Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13','',0,0); +UNLOCK TABLES; +/*!40000 ALTER TABLE `wp_comments` ENABLE KEYS */; + +-- +-- Table structure for table `wp_linkcategories` +-- + +DROP TABLE IF EXISTS `wp_linkcategories`; +CREATE TABLE `wp_linkcategories` ( + `cat_id` bigint(20) NOT NULL auto_increment, + `cat_name` tinytext NOT NULL, + `auto_toggle` enum('Y','N') NOT NULL default 'N', + `show_images` enum('Y','N') NOT NULL default 'Y', + `show_description` enum('Y','N') NOT NULL default 'N', + `show_rating` enum('Y','N') NOT NULL default 'Y', + `show_updated` enum('Y','N') NOT NULL default 'Y', + `sort_order` varchar(64) NOT NULL default 'rand', + `sort_desc` enum('Y','N') NOT NULL default 'N', + `text_before_link` varchar(128) NOT NULL default '
  • ', + `text_after_link` varchar(128) NOT NULL default '
    ', + `text_after_all` varchar(128) NOT NULL default '
  • ', + `list_limit` int(11) NOT NULL default '-1', + PRIMARY KEY (`cat_id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `wp_linkcategories` +-- + + +/*!40000 ALTER TABLE `wp_linkcategories` DISABLE KEYS */; +LOCK TABLES `wp_linkcategories` WRITE; +INSERT INTO `wp_linkcategories` VALUES (1,'Blogroll','N','Y','N','Y','Y','rand','N','
  • ','
    ','
  • ',-1),(2,'Blog0sphere','N','N','N','N','N','name','N','
  • ','
    ','
  • ',-1); +UNLOCK TABLES; +/*!40000 ALTER TABLE `wp_linkcategories` ENABLE KEYS */; + +-- +-- Table structure for table `wp_links` +-- + +DROP TABLE IF EXISTS `wp_links`; +CREATE TABLE `wp_links` ( + `link_id` bigint(20) NOT NULL auto_increment, + `link_url` varchar(255) NOT NULL default '', + `link_name` varchar(255) NOT NULL default '', + `link_image` varchar(255) NOT NULL default '', + `link_target` varchar(25) NOT NULL default '', + `link_category` int(11) NOT NULL default '0', + `link_description` varchar(255) NOT NULL default '', + `link_visible` enum('Y','N') NOT NULL default 'Y', + `link_owner` int(11) NOT NULL default '1', + `link_rating` int(11) NOT NULL default '0', + `link_updated` datetime NOT NULL default '0000-00-00 00:00:00', + `link_rel` varchar(255) NOT NULL default '', + `link_notes` mediumtext NOT NULL, + `link_rss` varchar(255) NOT NULL default '', + PRIMARY KEY (`link_id`), + KEY `link_category` (`link_category`), + KEY `link_visible` (`link_visible`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `wp_links` +-- + + +/*!40000 ALTER TABLE `wp_links` DISABLE KEYS */; +LOCK TABLES `wp_links` WRITE; +INSERT INTO `wp_links` VALUES (9,'http://fhwang.net','Francis Hwang','','',2,'Cool guy from NYC.rb','Y',2,0,'0000-00-00 00:00:00','','',''),(10,'http://redhanded.hobix.com','RedHanded','','',2,'One of the best Ruby Weblogs around','Y',2,0,'0000-00-00 00:00:00','','',''),(11,'http://stonecode.org/helios/blog','Staring at the Sun','','',2,'The Helios weblog','Y',2,0,'0000-00-00 00:00:00','','',''),(12,'http://project.ioni.st/','project.ioni.st','','',2,'Neat tumblelog from the Marcel Molina Jr. and Chad Fowler','Y',2,0,'0000-00-00 00:00:00','','',''),(13,'http://www.anarchaia.org/','Anarchaia','','',2,'Christian Neukirchen\'s tumblelog','Y',2,0,'0000-00-00 00:00:00','','',''),(14,'http://tiberius.newhavenrubyists.org','Eccentric Central','','',2,'','Y',2,0,'0000-00-00 00:00:00','','',''); +UNLOCK TABLES; +/*!40000 ALTER TABLE `wp_links` ENABLE KEYS */; + +-- +-- Table structure for table `wp_options` +-- + +DROP TABLE IF EXISTS `wp_options`; +CREATE TABLE `wp_options` ( + `option_id` bigint(20) NOT NULL auto_increment, + `blog_id` int(11) NOT NULL default '0', + `option_name` varchar(64) NOT NULL default '', + `option_can_override` enum('Y','N') NOT NULL default 'Y', + `option_type` int(11) NOT NULL default '1', + `option_value` longtext NOT NULL, + `option_width` int(11) NOT NULL default '20', + `option_height` int(11) NOT NULL default '8', + `option_description` tinytext NOT NULL, + `option_admin_level` int(11) NOT NULL default '1', + `autoload` enum('yes','no') NOT NULL default 'yes', + PRIMARY KEY (`option_id`,`blog_id`,`option_name`), + KEY `option_name` (`option_name`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `wp_options` +-- + + +/*!40000 ALTER TABLE `wp_options` DISABLE KEYS */; +LOCK TABLES `wp_options` WRITE; +INSERT INTO `wp_options` VALUES (1,0,'siteurl','Y',1,'http://www.stonecode.org/blog',20,8,'WordPress web address',1,'yes'),(2,0,'blogname','Y',1,'Stone Code Weblog',20,8,'Blog title',1,'yes'),(3,0,'blogdescription','Y',1,'The official rantage from Stone Code Productions',20,8,'Short tagline',1,'yes'),(4,0,'new_users_can_blog','Y',1,'1',20,8,'',1,'yes'),(5,0,'users_can_register','Y',1,'1',20,8,'',1,'yes'),(6,0,'admin_email','Y',1,'gregory.t.brown@gmail.com',20,8,'',1,'yes'),(7,0,'start_of_week','Y',1,'0',20,8,'',1,'yes'),(8,0,'use_balanceTags','Y',1,'1',20,8,'',1,'yes'),(9,0,'use_smilies','Y',1,'1',20,8,'',1,'yes'),(10,0,'require_name_email','Y',1,'1',20,8,'',1,'yes'),(11,0,'comments_notify','Y',1,'1',20,8,'',1,'yes'),(12,0,'posts_per_rss','Y',1,'10',20,8,'',1,'yes'),(13,0,'rss_excerpt_length','Y',1,'50',20,8,'',1,'yes'),(14,0,'rss_use_excerpt','Y',1,'0',20,8,'',1,'yes'),(15,0,'use_fileupload','Y',1,'0',20,8,'',1,'yes'),(16,0,'fileupload_realpath','Y',1,'/home/.quran/oracalius/stonecode.org/blog/wp-content',20,8,'',1,'yes'),(17,0,'fileupload_url','Y',1,'/wp-content',20,8,'',1,'yes'),(18,0,'fileupload_allowedtypes','Y',1,'jpg jpeg gif png',20,8,'',1,'yes'),(19,0,'fileupload_maxk','Y',1,'300',20,8,'',1,'yes'),(20,0,'fileupload_minlevel','Y',1,'6',20,8,'',1,'yes'),(21,0,'mailserver_url','Y',1,'mail.stonecode.org',20,8,'',1,'yes'),(22,0,'mailserver_login','Y',1,'m9427984',20,8,'',1,'yes'),(23,0,'mailserver_pass','Y',1,'*YT*tL',20,8,'',1,'yes'),(24,0,'mailserver_port','Y',1,'110',20,8,'',1,'yes'),(25,0,'default_category','Y',1,'2',20,8,'',1,'yes'),(26,0,'default_comment_status','Y',1,'open',20,8,'',1,'yes'),(27,0,'default_ping_status','Y',1,'open',20,8,'',1,'yes'),(28,0,'default_pingback_flag','Y',1,'1',20,8,'',1,'yes'),(29,0,'default_post_edit_rows','Y',1,'9',20,8,'',1,'yes'),(30,0,'posts_per_page','Y',1,'10',20,8,'',1,'yes'),(31,0,'what_to_show','Y',1,'posts',20,8,'',1,'yes'),(32,0,'date_format','Y',1,'F j, Y',20,8,'',1,'yes'),(33,0,'time_format','Y',1,'g:i a',20,8,'',1,'yes'),(34,0,'links_updated_date_format','Y',1,'F j, Y g:i a',20,8,'',1,'yes'),(35,0,'links_recently_updated_prepend','Y',1,'',20,8,'',1,'yes'),(36,0,'links_recently_updated_append','Y',1,'',20,8,'',1,'yes'),(37,0,'links_recently_updated_time','Y',1,'120',20,8,'',1,'yes'),(38,0,'comment_moderation','Y',1,'',20,8,'',1,'yes'),(39,0,'moderation_notify','Y',1,'1',20,8,'',1,'yes'),(40,0,'permalink_structure','Y',1,'',20,8,'',1,'yes'),(41,0,'gzipcompression','Y',1,'0',20,8,'',1,'yes'),(42,0,'hack_file','Y',1,'0',20,8,'',1,'yes'),(43,0,'blog_charset','Y',1,'UTF-8',20,8,'',1,'yes'),(44,0,'moderation_keys','Y',1,'',20,8,'',1,'no'),(45,0,'active_plugins','Y',1,'a:1:{i:0;s:0:\"\";}',20,8,'',1,'yes'),(46,0,'home','Y',1,'http://www.stonecode.org/blog',20,8,'',1,'yes'),(47,0,'category_base','Y',1,'',20,8,'',1,'yes'),(48,0,'ping_sites','Y',1,'http://rpc.pingomatic.com/',20,8,'',1,'yes'),(49,0,'advanced_edit','Y',1,'0',20,8,'',1,'yes'),(50,0,'comment_max_links','Y',1,'2',20,8,'',1,'yes'),(51,0,'default_email_category','Y',1,'2',20,8,'Posts by email go to this category',1,'yes'),(52,0,'recently_edited','Y',1,'',20,8,'',1,'no'),(53,0,'use_linksupdate','Y',1,'0',20,8,'',1,'yes'),(54,0,'template','Y',1,'ocadia',20,8,'',1,'yes'),(55,0,'stylesheet','Y',1,'ocadia',20,8,'',1,'yes'),(56,0,'comment_whitelist','Y',1,'',20,8,'',1,'yes'),(57,0,'page_uris','Y',1,'',20,8,'',1,'yes'),(58,0,'blacklist_keys','Y',1,'',20,8,'',1,'no'),(59,0,'comment_registration','Y',1,'',20,8,'',1,'yes'),(60,0,'open_proxy_check','Y',1,'1',20,8,'',1,'yes'),(61,0,'rss_language','Y',1,'en',20,8,'',1,'yes'),(62,0,'html_type','Y',1,'text/html',20,8,'',1,'yes'),(63,0,'use_trackback','Y',1,'0',20,8,'',1,'yes'),(64,0,'gmt_offset','Y',1,'-4',20,8,'',1,'yes'),(65,0,'rss_5a959cb60be478ebd9c16622e37ba983','Y',1,'O:9:\"magpierss\":17:{s:6:\"parser\";i:0;s:12:\"current_item\";a:0:{}s:5:\"items\";a:2:{i:0;a:9:{s:5:\"title\";s:44:\"Ruby Buzz: \"Who needs a fancy media player?\"\";s:4:\"link\";s:61:\"http://www.artima.com/forums/flat.jsp?forum=123&thread=142468\";s:4:\"guid\";s:61:\"http://www.artima.com/forums/flat.jsp?forum=123&thread=142468\";s:11:\"description\";s:531:\"

    ... Who needs a fancy media player? ...

    Ruby Buzz \"Technorati

    \";s:7:\"pubdate\";s:29:\"Thu, 29 Dec 2005 07:30:03 GMT\";s:4:\"tapi\";a:1:{s:11:\"linkcreated\";s:23:\"2005-12-29 07:30:03 GMT\";}s:8:\"comments\";s:99:\"http://www.technorati.com/search/www.artima.com%2Fforums%2Fflat.jsp%3Fforum%3D123%26thread%3D142468\";s:6:\"source\";s:9:\"Ruby Buzz\";s:7:\"summary\";s:531:\"

    ... Who needs a fancy media player? ...

    Ruby Buzz \"Technorati

    \";}i:1;a:9:{s:5:\"title\";s:26:\"Ruby Buzz: \"Some sad news\"\";s:4:\"link\";s:61:\"http://www.artima.com/forums/flat.jsp?forum=123&thread=142263\";s:4:\"guid\";s:61:\"http://www.artima.com/forums/flat.jsp?forum=123&thread=142263\";s:11:\"description\";s:513:\"

    ... Some sad news ...

    Ruby Buzz \"Technorati

    \";s:7:\"pubdate\";s:29:\"Mon, 26 Dec 2005 23:37:04 GMT\";s:4:\"tapi\";a:1:{s:11:\"linkcreated\";s:23:\"2005-12-26 23:37:04 GMT\";}s:8:\"comments\";s:99:\"http://www.technorati.com/search/www.artima.com%2Fforums%2Fflat.jsp%3Fforum%3D123%26thread%3D142263\";s:6:\"source\";s:9:\"Ruby Buzz\";s:7:\"summary\";s:513:\"

    ... Some sad news ...

    Ruby Buzz \"Technorati

    \";}}s:7:\"channel\";a:10:{s:9:\"generator\";s:15:\"Technorati v1.0\";s:9:\"webmaster\";s:43:\"support@technorati.com (Technorati Support)\";s:4:\"docs\";s:37:\"http://blogs.law.harvard.edu/tech/rss\";s:3:\"ttl\";s:2:\"60\";s:5:\"title\";s:40:\"Technorati Search for: Stone Code Weblog\";s:4:\"link\";s:70:\"http://www.technorati.com/search/http%3A%2F%2Fwww.stonecode.org%2Fblog\";s:11:\"description\";s:56:\"Technorati search for all blogs citing Stone Code Weblog\";s:7:\"pubdate\";s:29:\"Sat, 31 Dec 2005 16:15:50 GMT\";s:4:\"tapi\";a:3:{s:4:\"rank\";s:6:\"367179\";s:12:\"inboundblogs\";s:1:\"8\";s:12:\"inboundlinks\";s:2:\"24\";}s:7:\"tagline\";s:56:\"Technorati search for all blogs citing Stone Code Weblog\";}s:9:\"textinput\";a:4:{s:5:\"title\";s:17:\"Search Technorati\";s:11:\"description\";s:43:\"Search millions of blogs for the latest on:\";s:4:\"name\";s:1:\"s\";s:4:\"link\";s:36:\"http://www.technorati.com/search.php\";}s:5:\"image\";a:3:{s:3:\"url\";s:50:\"http://static.technorati.com/pix/logos/logo_sm.gif\";s:5:\"title\";s:15:\"Technorati logo\";s:4:\"link\";s:25:\"http://www.technorati.com\";}s:9:\"feed_type\";s:3:\"RSS\";s:12:\"feed_version\";s:3:\"2.0\";s:5:\"stack\";a:0:{}s:9:\"inchannel\";b:0;s:6:\"initem\";b:0;s:9:\"incontent\";b:0;s:11:\"intextinput\";b:0;s:7:\"inimage\";b:0;s:13:\"current_field\";s:0:\"\";s:17:\"current_namespace\";b:0;s:19:\"_CONTENT_CONSTRUCTS\";a:6:{i:0;s:7:\"content\";i:1;s:7:\"summary\";i:2;s:4:\"info\";i:3;s:5:\"title\";i:4;s:7:\"tagline\";i:5;s:9:\"copyright\";}}',20,8,'',1,'no'),(66,0,'rss_5a959cb60be478ebd9c16622e37ba983_ts','Y',1,'1136045750',20,8,'',1,'no'),(67,0,'rss_0ff4b43bd116a9d8720d689c80e7dfd4','Y',1,'O:9:\"magpierss\":19:{s:6:\"parser\";i:0;s:12:\"current_item\";a:0:{}s:5:\"items\";a:5:{i:0;a:12:{s:5:\"title\";s:11:\"WordPress 2\";s:4:\"link\";s:45:\"http://wordpress.org/development/2005/12/wp2/\";s:8:\"comments\";s:54:\"http://wordpress.org/development/2005/12/wp2/#comments\";s:7:\"pubdate\";s:31:\"Sat, 31 Dec 2005 00:47:10 +0000\";s:2:\"dc\";a:1:{s:7:\"creator\";s:4:\"Matt\";}s:8:\"category\";s:19:\"DevelopmentReleases\";s:4:\"guid\";s:45:\"http://wordpress.org/development/2005/12/wp2/\";s:11:\"description\";s:302:\"The WordPress community is very proud to present the next generation of WordPress to the world, our 2.0 “Duke” release, named in honor of jazz pianist and composer Duke Ellington. We’ve been working long and hard to bring you this release, and I hope you enjoy using it as [...]\";s:7:\"content\";a:1:{s:7:\"encoded\";s:8671:\"

    The WordPress community is very proud to present the next generation of WordPress to the world, our 2.0 “Duke” release, named in honor of jazz pianist and composer Duke Ellington. We’ve been working long and hard to bring you this release, and I hope you enjoy using it as much as we’e enjoyed working on it. In this release we’ve focused a tremendous amount on what we believe to be the core of blogging — the writing interface. Before you upgrade from an earlier version, remember that this is a major release and thousands of lines of code have changed. Before upgrading it’s always good, just in case, to make a backup of your database and WordPress files. It only takes a few minutes and gives you a total safety net if for whatever reason things don’t work. It is also probably a good idea to turn off your plugins, and activate them one-by-one after you’ve upgraded. Without further ado, you can download WordPress 2 right now. Read on for more information about what we think you’ll love about Duke.

    \n

    \n

    User Features

    \n
      \n
    • Completely Redesigned Backend — The first thing you’ll notice when you login to your blog is the backend has been completely overhauled for both aesthetics and usability. This is the first iteration of exciting things to come from the Shuttle team of designers that has been volunteering their time, and look for even more aesthetic improvements in the future.
    • \n
    • Faster Administration — Call it AJAX, call it DHTML, call it Larry, but we’ve paid close attention to streamlining some of the most common tasks in managing your blog. For example if you’re writing a post and you can add categories on the fly, much like tagging in Flickr. Also instead of having two separate UIs for “simple” and “advanced” posting, we’ve combined them and let you customize the layout of the page on the fly by dragging and dropping the dialogs around. It saves where you put things so when you return it’s just like you left it. When you delete a comment or category it will fade out without a page load.
    • \n
    • WYSIWYG Editing — WP dev Andy Skelton and the TinyMCE team have done a tremendous amount of work to bring a smooth WYSIWYG editing experience to WordPress. As code purists, we are very picky about what kind of HTML is generated, and while it’s not perfect yet (for instance nested lists can cause trouble) for 95% of what you do post-to-post the WYSIWYG should save you time. And if it doesn’t, you can turn it off on your profile page. One note: Safari and older versions of Opera, both fantastic browsers, don’t yet support everything that’s needed to do WYSIWYG, but we fully expect new versions of those browsers will continue to improve their standards support, so it may just be a matter of time.
    • \n
    • Included Spam and Backup Plugins — We’ve included two of the most popular WordPress plugins: Skippy’s DB backup can backup your database to a file and optionally email you a copy; Akismet is a distributed anti-spam system which gets smarter the more people use it.
    • \n
    • Resizable Editing — This is one of my personal favorite features. Ever been writing a post and that textarea seemed a little small? Happens to me all the time, and our new rich text editor includes a feature that lets you resize the editor on-the-fly by clicking on the corner, just like a regular window.
    • \n
    • Inline Uploading — We’ve optimized our uploader for image, audio, and video files and put it inline with the posting screen. You don’t have to bounce around any more when writing a post! It also will organize your files for you as you upload them to make them easier to find later. On the backend, each uploaded file is actually a “sub-post” so it can have individual comments and pingbacks, its own permalink, and even a custom template based on what type of file it is. You can click on attached files to get a menu of options, or if you’re on Firefox you can drag and drop them into your WYSIWYG editor.
    • \n
    • Faster Posting — In the past if you were linking to a number of posts or pinging a lot of update services, your posting time could appear to slow to a crawl even though everything was instantly done on the backend. We’ve modified how this works now so posting should be near-instantaneous, like everything else in WordPress.
    • \n
    • Post Preview — Another enhancement to the post screen, now when you save a post it shows a live preview of how the post would look on your site, with the stylesheet and theme and everything. No more publishing a post just to see if it works.
    • \n
    • Streamlined Importing — We’ve rewritten our import system from the ground up to be much easier to use (you no longer have to edit files), put it behind authentication, and also made it easy for new importers to be dropped into the system, much like plugins.
    • \n
    • User Roles — We had a ton of feedback on our old numerical user level system. No one was exactly sure what those numbers meant! We’ve distilled the basic functions into a set of roles — such as administrator, editor, contributor — that make it easier to understand what sort of capabilities you’re giving your blog’s users. The new system is completely pluggable too, so plugins can modify roles and create groups that have access to certain things.
    • \n
    • Header Customization — If you’re tired of the blue header in the default theme, you can now change the colors and text of it, which we’ve included as a demo of some of the new features available to theme authors.
    • \n
    \n

    Developer Features

    \n

    On the backend we’ve done a ton of changes to clean up code, make things more consistent, and enable a lot of new types of applications to be built on top of WordPress.

    \n
      \n
    • User Level Options — You can now store options on a per-user level rather than having them apply for the entire blog. An example of this in WP2 is with the rich text editor, which can be turned on or off per a user’s discretion.
    • \n
    • Improved Abstraction — We’ve eliminated almost all direct SQL queries from the code and moved them to functions and classes that make the entire program more consistent.
    • \n
    • Built-in Caching — WordPress now includes a completely pluggable object cache system that cuts the number of queries most pages do in half. By default it is disk-based, but there is already a plugin to use memcached and we expect more are on their way. We’ve only begun to tap into this.
    • \n
    • Plugin Hooks Galore — We’ve added hooks for plugin authors wherever we could think to, so what you’re able to do in the new system is pretty dramatic. Ne features like the WYSIWYG and the inline uploader are completely pluggable and can be replaced entirely.
    • \n
    • Import Framework — The new import framework allows you to create an importer with about a third of the code you used to need, and it can have a consistent interface with no extra work.
    • \n
    • Theme Functions — Themes can now include a functions.php file that will now be loaded like a plugin attached to the theme.
    • \n
    • Theme preview images — You can now include a screenshot of the theme with the download so in the WP interface your users will see a quick preview of what it looks like.
    • \n
    • Hundreds and Hundreds of Bug Fixes2.0 has hundreds of tracked bugs and enhancements, many that are very subtle.
    • \n
    \n

    You may have noticed our design has changed quite a bit. We’ve also moved WordPress.org to a newer, faster server. There were a few issues with the move which is why we’ve held off for a few days on announcing 2.0. Everything seems to be smooth sailing now.\n

    \n\";}s:3:\"wfw\";a:1:{s:10:\"commentrss\";s:50:\"http://wordpress.org/development/2005/12/wp2/feed/\";}s:7:\"summary\";s:302:\"The WordPress community is very proud to present the next generation of WordPress to the world, our 2.0 “Duke” release, named in honor of jazz pianist and composer Duke Ellington. We’ve been working long and hard to bring you this release, and I hope you enjoy using it as [...]\";s:12:\"atom_content\";s:8671:\"

    The WordPress community is very proud to present the next generation of WordPress to the world, our 2.0 “Duke” release, named in honor of jazz pianist and composer Duke Ellington. We’ve been working long and hard to bring you this release, and I hope you enjoy using it as much as we’e enjoyed working on it. In this release we’ve focused a tremendous amount on what we believe to be the core of blogging — the writing interface. Before you upgrade from an earlier version, remember that this is a major release and thousands of lines of code have changed. Before upgrading it’s always good, just in case, to make a backup of your database and WordPress files. It only takes a few minutes and gives you a total safety net if for whatever reason things don’t work. It is also probably a good idea to turn off your plugins, and activate them one-by-one after you’ve upgraded. Without further ado, you can download WordPress 2 right now. Read on for more information about what we think you’ll love about Duke.

    \n

    \n

    User Features

    \n
      \n
    • Completely Redesigned Backend — The first thing you’ll notice when you login to your blog is the backend has been completely overhauled for both aesthetics and usability. This is the first iteration of exciting things to come from the Shuttle team of designers that has been volunteering their time, and look for even more aesthetic improvements in the future.
    • \n
    • Faster Administration — Call it AJAX, call it DHTML, call it Larry, but we’ve paid close attention to streamlining some of the most common tasks in managing your blog. For example if you’re writing a post and you can add categories on the fly, much like tagging in Flickr. Also instead of having two separate UIs for “simple” and “advanced” posting, we’ve combined them and let you customize the layout of the page on the fly by dragging and dropping the dialogs around. It saves where you put things so when you return it’s just like you left it. When you delete a comment or category it will fade out without a page load.
    • \n
    • WYSIWYG Editing — WP dev Andy Skelton and the TinyMCE team have done a tremendous amount of work to bring a smooth WYSIWYG editing experience to WordPress. As code purists, we are very picky about what kind of HTML is generated, and while it’s not perfect yet (for instance nested lists can cause trouble) for 95% of what you do post-to-post the WYSIWYG should save you time. And if it doesn’t, you can turn it off on your profile page. One note: Safari and older versions of Opera, both fantastic browsers, don’t yet support everything that’s needed to do WYSIWYG, but we fully expect new versions of those browsers will continue to improve their standards support, so it may just be a matter of time.
    • \n
    • Included Spam and Backup Plugins — We’ve included two of the most popular WordPress plugins: Skippy’s DB backup can backup your database to a file and optionally email you a copy; Akismet is a distributed anti-spam system which gets smarter the more people use it.
    • \n
    • Resizable Editing — This is one of my personal favorite features. Ever been writing a post and that textarea seemed a little small? Happens to me all the time, and our new rich text editor includes a feature that lets you resize the editor on-the-fly by clicking on the corner, just like a regular window.
    • \n
    • Inline Uploading — We’ve optimized our uploader for image, audio, and video files and put it inline with the posting screen. You don’t have to bounce around any more when writing a post! It also will organize your files for you as you upload them to make them easier to find later. On the backend, each uploaded file is actually a “sub-post” so it can have individual comments and pingbacks, its own permalink, and even a custom template based on what type of file it is. You can click on attached files to get a menu of options, or if you’re on Firefox you can drag and drop them into your WYSIWYG editor.
    • \n
    • Faster Posting — In the past if you were linking to a number of posts or pinging a lot of update services, your posting time could appear to slow to a crawl even though everything was instantly done on the backend. We’ve modified how this works now so posting should be near-instantaneous, like everything else in WordPress.
    • \n
    • Post Preview — Another enhancement to the post screen, now when you save a post it shows a live preview of how the post would look on your site, with the stylesheet and theme and everything. No more publishing a post just to see if it works.
    • \n
    • Streamlined Importing — We’ve rewritten our import system from the ground up to be much easier to use (you no longer have to edit files), put it behind authentication, and also made it easy for new importers to be dropped into the system, much like plugins.
    • \n
    • User Roles — We had a ton of feedback on our old numerical user level system. No one was exactly sure what those numbers meant! We’ve distilled the basic functions into a set of roles — such as administrator, editor, contributor — that make it easier to understand what sort of capabilities you’re giving your blog’s users. The new system is completely pluggable too, so plugins can modify roles and create groups that have access to certain things.
    • \n
    • Header Customization — If you’re tired of the blue header in the default theme, you can now change the colors and text of it, which we’ve included as a demo of some of the new features available to theme authors.
    • \n
    \n

    Developer Features

    \n

    On the backend we’ve done a ton of changes to clean up code, make things more consistent, and enable a lot of new types of applications to be built on top of WordPress.

    \n
      \n
    • User Level Options — You can now store options on a per-user level rather than having them apply for the entire blog. An example of this in WP2 is with the rich text editor, which can be turned on or off per a user’s discretion.
    • \n
    • Improved Abstraction — We’ve eliminated almost all direct SQL queries from the code and moved them to functions and classes that make the entire program more consistent.
    • \n
    • Built-in Caching — WordPress now includes a completely pluggable object cache system that cuts the number of queries most pages do in half. By default it is disk-based, but there is already a plugin to use memcached and we expect more are on their way. We’ve only begun to tap into this.
    • \n
    • Plugin Hooks Galore — We’ve added hooks for plugin authors wherever we could think to, so what you’re able to do in the new system is pretty dramatic. Ne features like the WYSIWYG and the inline uploader are completely pluggable and can be replaced entirely.
    • \n
    • Import Framework — The new import framework allows you to create an importer with about a third of the code you used to need, and it can have a consistent interface with no extra work.
    • \n
    • Theme Functions — Themes can now include a functions.php file that will now be loaded like a plugin attached to the theme.
    • \n
    • Theme preview images — You can now include a screenshot of the theme with the download so in the WP interface your users will see a quick preview of what it looks like.
    • \n
    • Hundreds and Hundreds of Bug Fixes2.0 has hundreds of tracked bugs and enhancements, many that are very subtle.
    • \n
    \n

    You may have noticed our design has changed quite a bit. We’ve also moved WordPress.org to a newer, faster server. There were a few issues with the move which is why we’ve held off for a few days on announcing 2.0. Everything seems to be smooth sailing now.\n

    \n\";}i:1;a:12:{s:5:\"title\";s:18:\"WordPress on Yahoo\";s:4:\"link\";s:60:\"http://wordpress.org/development/2005/12/wordpress-on-yahoo/\";s:8:\"comments\";s:69:\"http://wordpress.org/development/2005/12/wordpress-on-yahoo/#comments\";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 17:53:06 +0000\";s:2:\"dc\";a:1:{s:7:\"creator\";s:4:\"Matt\";}s:8:\"category\";s:18:\"DevelopmentHosting\";s:4:\"guid\";s:60:\"http://wordpress.org/development/2005/12/wordpress-on-yahoo/\";s:11:\"description\";s:328:\"As many of you know, we’re constantly tweaking and updating our web hosting page based on feedback we get from you. Well today we’re very excited to announce we’re adding a new host to the page with a familiar name - Yahoo! We’ve been working with the Yahoo Small Business team to create a solution [...]\";s:7:\"content\";a:1:{s:7:\"encoded\";s:2569:\"

    As many of you know, we’re constantly tweaking and updating our web hosting page based on feedback we get from you. Well today we’re very excited to announce we’re adding a new host to the page with a familiar name - Yahoo! We’ve been working with the Yahoo Small Business team to create a solution that gives professional bloggers exactly what they want from their hosting providers.

    \n

    When we started, Yahoo asked “What would the perfect blog host do?” and their team has been really amazing in executing on a really kick-ass platform for serious bloggers. It took a little while, but slow cooking makes good eating. (Like WordPress 2.0!)

    \n

    We think the hosting is good for all the baseline features you should expect — tons of storage, bandwidth, Yahoo reliability, etc. (You probably heard all about that in their Movable Type announcement last week.) However we think they’re worth featuring because of three key things:

    \n\n

    Guy Yalif from Yahoo says, “We believe that by adding WordPress’ blogging application to our leading web hosting product, we are providing a top notch, scalable, and reliable solution for less than $12 per month.”

    \n

    We think the above makes a very compelling case for WordPress users to check out Yahoo hosting, and see what we believe is the best WordPress hosting experience on the Web. As always, if you have any feedback on Yahoo or any other host we feature, please let us know.\n

    \n\";}s:3:\"wfw\";a:1:{s:10:\"commentrss\";s:65:\"http://wordpress.org/development/2005/12/wordpress-on-yahoo/feed/\";}s:7:\"summary\";s:328:\"As many of you know, we’re constantly tweaking and updating our web hosting page based on feedback we get from you. Well today we’re very excited to announce we’re adding a new host to the page with a familiar name - Yahoo! We’ve been working with the Yahoo Small Business team to create a solution [...]\";s:12:\"atom_content\";s:2569:\"

    As many of you know, we’re constantly tweaking and updating our web hosting page based on feedback we get from you. Well today we’re very excited to announce we’re adding a new host to the page with a familiar name - Yahoo! We’ve been working with the Yahoo Small Business team to create a solution that gives professional bloggers exactly what they want from their hosting providers.

    \n

    When we started, Yahoo asked “What would the perfect blog host do?” and their team has been really amazing in executing on a really kick-ass platform for serious bloggers. It took a little while, but slow cooking makes good eating. (Like WordPress 2.0!)

    \n

    We think the hosting is good for all the baseline features you should expect — tons of storage, bandwidth, Yahoo reliability, etc. (You probably heard all about that in their Movable Type announcement last week.) However we think they’re worth featuring because of three key things:

    \n\n

    Guy Yalif from Yahoo says, “We believe that by adding WordPress’ blogging application to our leading web hosting product, we are providing a top notch, scalable, and reliable solution for less than $12 per month.”

    \n

    We think the above makes a very compelling case for WordPress users to check out Yahoo hosting, and see what we believe is the best WordPress hosting experience on the Web. As always, if you have any feedback on Yahoo or any other host we feature, please let us know.\n

    \n\";}i:2;a:12:{s:5:\"title\";s:21:\"2.0 Release Candidate\";s:4:\"link\";s:72:\"http://wordpress.org/development/2005/12/wordpress-20-release-candidate/\";s:8:\"comments\";s:81:\"http://wordpress.org/development/2005/12/wordpress-20-release-candidate/#comments\";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 07:52:04 +0000\";s:2:\"dc\";a:1:{s:7:\"creator\";s:4:\"Ryan\";}s:8:\"category\";s:19:\"DevelopmentReleases\";s:4:\"guid\";s:72:\"http://wordpress.org/development/2005/12/wordpress-20-release-candidate/\";s:11:\"description\";s:322:\"The next release of WordPress is drawing near. Please help us shake out any last remaining bugs by downloading and testing the 2.0 Release Candidate. If all goes well, the Release Candidate will become 2.0 final. We’re almost there. Download, test, and head over to the Beta Forum to let us know if the Release [...]\";s:7:\"content\";a:1:{s:7:\"encoded\";s:607:\"

    The next release of WordPress is drawing near. Please help us shake out any last remaining bugs by downloading and testing the 2.0 Release Candidate. If all goes well, the Release Candidate will become 2.0 final. We’re almost there. Download, test, and head over to the Beta Forum to let us know if the Release Candidate is ready for prime time. Downloads taken down. Go get the real thing!\n

    \n\";}s:3:\"wfw\";a:1:{s:10:\"commentrss\";s:77:\"http://wordpress.org/development/2005/12/wordpress-20-release-candidate/feed/\";}s:7:\"summary\";s:322:\"The next release of WordPress is drawing near. Please help us shake out any last remaining bugs by downloading and testing the 2.0 Release Candidate. If all goes well, the Release Candidate will become 2.0 final. We’re almost there. Download, test, and head over to the Beta Forum to let us know if the Release [...]\";s:12:\"atom_content\";s:607:\"

    The next release of WordPress is drawing near. Please help us shake out any last remaining bugs by downloading and testing the 2.0 Release Candidate. If all goes well, the Release Candidate will become 2.0 final. We’re almost there. Download, test, and head over to the Beta Forum to let us know if the Release Candidate is ready for prime time. Downloads taken down. Go get the real thing!\n

    \n\";}i:3;a:12:{s:5:\"title\";s:32:\"Don?t Panic! WordPress Is Secure\";s:4:\"link\";s:61:\"http://wordpress.org/development/2005/11/wordpress-is-secure/\";s:8:\"comments\";s:70:\"http://wordpress.org/development/2005/11/wordpress-is-secure/#comments\";s:7:\"pubdate\";s:31:\"Tue, 08 Nov 2005 17:02:27 +0000\";s:2:\"dc\";a:1:{s:7:\"creator\";s:6:\"Dougal\";}s:8:\"category\";s:19:\"DevelopmentSecurity\";s:4:\"guid\";s:61:\"http://wordpress.org/development/2005/11/wordpress-is-secure/\";s:11:\"description\";s:353:\"There is news of a worm which uses a vulnerability in the PHPXMLRPC libraries to spread a computer virus. Some articles are pointing to out-of-date information claiming that WordPress 1.5 is vulnerable. That is incorrect. WordPress 1.5 or higher is safe. Since the release of version 1.5, WordPress has used a completely different XML-RPC library, [...]\";s:7:\"content\";a:1:{s:7:\"encoded\";s:1332:\"

    There is news of a worm which uses a vulnerability in the PHPXMLRPC libraries to spread a computer virus. Some articles are pointing to out-of-date information claiming that WordPress 1.5 is vulnerable. That is incorrect. WordPress 1.5 or higher is safe. Since the release of version 1.5, WordPress has used a completely different XML-RPC library, called IXR.

    \n

    Older WP versions (1.2.x and earlier) are vulnerable, however. If for some reason you are still running a pre-1.5 version of WordPress, you should upgrade immediately to the latest version, WordPress 1.5.2 “Strayhorn”. If upgrading poses a problem for some reason, and if you don’t need pingbacks or blog client API functionality, simply delete the class-xmlrpc.php and class-xmlrpcs.php files from your installation’s wp-includes directory (but you really should upgrade).

    \n

    Also if you ever come across something you feel might be a security problem in WordPress, please send a note to the special address we’ve set up for security purposes and we will address it as quickly as possible.

    \n\";}s:3:\"wfw\";a:1:{s:10:\"commentrss\";s:66:\"http://wordpress.org/development/2005/11/wordpress-is-secure/feed/\";}s:7:\"summary\";s:353:\"There is news of a worm which uses a vulnerability in the PHPXMLRPC libraries to spread a computer virus. Some articles are pointing to out-of-date information claiming that WordPress 1.5 is vulnerable. That is incorrect. WordPress 1.5 or higher is safe. Since the release of version 1.5, WordPress has used a completely different XML-RPC library, [...]\";s:12:\"atom_content\";s:1332:\"

    There is news of a worm which uses a vulnerability in the PHPXMLRPC libraries to spread a computer virus. Some articles are pointing to out-of-date information claiming that WordPress 1.5 is vulnerable. That is incorrect. WordPress 1.5 or higher is safe. Since the release of version 1.5, WordPress has used a completely different XML-RPC library, called IXR.

    \n

    Older WP versions (1.2.x and earlier) are vulnerable, however. If for some reason you are still running a pre-1.5 version of WordPress, you should upgrade immediately to the latest version, WordPress 1.5.2 “Strayhorn”. If upgrading poses a problem for some reason, and if you don’t need pingbacks or blog client API functionality, simply delete the class-xmlrpc.php and class-xmlrpcs.php files from your installation’s wp-includes directory (but you really should upgrade).

    \n

    Also if you ever come across something you feel might be a security problem in WordPress, please send a note to the special address we’ve set up for security purposes and we will address it as quickly as possible.

    \n\";}i:4;a:12:{s:5:\"title\";s:8:\"Bug Hunt\";s:4:\"link\";s:50:\"http://wordpress.org/development/2005/11/bug-hunt/\";s:8:\"comments\";s:59:\"http://wordpress.org/development/2005/11/bug-hunt/#comments\";s:7:\"pubdate\";s:31:\"Thu, 03 Nov 2005 16:28:04 +0000\";s:2:\"dc\";a:1:{s:7:\"creator\";s:4:\"Matt\";}s:8:\"category\";s:17:\"DevelopmentEvents\";s:4:\"guid\";s:50:\"http://wordpress.org/development/2005/11/bug-hunt/\";s:11:\"description\";s:347:\"You are invited to the WordPress Bug Hunt on Saturday, November 5th, 2005!\nWhether you’re a die-hard WordPress hacker or just looking to gain some familiarity with WordPress internals, we need your help! Join us in #wordpress-bugs on irc.freenode.net as we triage and eliminate as many bugs as possible. Work with us to confirm bugs, [...]\";s:7:\"content\";a:1:{s:7:\"encoded\";s:1058:\"

    You are invited to the WordPress Bug Hunt on Saturday, November 5th, 2005!

    \n

    Whether you’re a die-hard WordPress hacker or just looking to gain some familiarity with WordPress internals, we need your help! Join us in #wordpress-bugs on irc.freenode.net as we triage and eliminate as many bugs as possible. Work with us to confirm bugs, submit and test patches, and generally geek out.

    \n

    All you need to bring is a text editor, and an installation of WordPress 1.6-ALPHA! We’ll provide the snacks, and manage the schedule.

    \n

    We’ll start as soon as you arrive, so please be prompt!

    \n

    See WordPress Bug Hunts on the Codex for additional information. This looks to be a recurring effort, so if you can’t attend this one, stay tuned for future Bug Hunts! We’ll also be working on Sunday, though the main thrust of the event is Saturday.

    \n\";}s:3:\"wfw\";a:1:{s:10:\"commentrss\";s:55:\"http://wordpress.org/development/2005/11/bug-hunt/feed/\";}s:7:\"summary\";s:347:\"You are invited to the WordPress Bug Hunt on Saturday, November 5th, 2005!\nWhether you’re a die-hard WordPress hacker or just looking to gain some familiarity with WordPress internals, we need your help! Join us in #wordpress-bugs on irc.freenode.net as we triage and eliminate as many bugs as possible. Work with us to confirm bugs, [...]\";s:12:\"atom_content\";s:1058:\"

    You are invited to the WordPress Bug Hunt on Saturday, November 5th, 2005!

    \n

    Whether you’re a die-hard WordPress hacker or just looking to gain some familiarity with WordPress internals, we need your help! Join us in #wordpress-bugs on irc.freenode.net as we triage and eliminate as many bugs as possible. Work with us to confirm bugs, submit and test patches, and generally geek out.

    \n

    All you need to bring is a text editor, and an installation of WordPress 1.6-ALPHA! We’ll provide the snacks, and manage the schedule.

    \n

    We’ll start as soon as you arrive, so please be prompt!

    \n

    See WordPress Bug Hunts on the Codex for additional information. This looks to be a recurring effort, so if you can’t attend this one, stay tuned for future Bug Hunts! We’ll also be working on Sunday, though the main thrust of the event is Saturday.

    \n\";}}s:7:\"channel\";a:7:{s:5:\"title\";s:26:\"WordPress Development Blog\";s:4:\"link\";s:32:\"http://wordpress.org/development\";s:11:\"description\";s:33:\"WordPress development and updates\";s:7:\"pubdate\";s:31:\"Sat, 31 Dec 2005 00:53:37 +0000\";s:9:\"generator\";s:27:\"http://wordpress.org/?v=2.0\";s:8:\"language\";s:2:\"en\";s:7:\"tagline\";s:33:\"WordPress development and updates\";}s:9:\"textinput\";a:0:{}s:5:\"image\";a:0:{}s:9:\"feed_type\";s:3:\"RSS\";s:12:\"feed_version\";s:3:\"2.0\";s:5:\"stack\";a:0:{}s:9:\"inchannel\";b:0;s:6:\"initem\";b:0;s:9:\"incontent\";b:0;s:11:\"intextinput\";b:0;s:7:\"inimage\";b:0;s:13:\"current_field\";s:0:\"\";s:17:\"current_namespace\";b:0;s:19:\"_CONTENT_CONSTRUCTS\";a:6:{i:0;s:7:\"content\";i:1;s:7:\"summary\";i:2;s:4:\"info\";i:3;s:5:\"title\";i:4;s:7:\"tagline\";i:5;s:9:\"copyright\";}s:13:\"last_modified\";s:31:\"Sat, 31 Dec 2005 00:53:37 GMT\r\n\";s:4:\"etag\";s:36:\"\"6569bbc88dfeb6b78e63bc3cb84dd344\"\r\n\";}',20,8,'',1,'no'),(68,0,'rss_0ff4b43bd116a9d8720d689c80e7dfd4_ts','Y',1,'1136045750',20,8,'',1,'no'),(69,0,'rss_867bd5c64f85878d03a060509cd2f92c','Y',1,'O:9:\"magpierss\":19:{s:6:\"parser\";i:0;s:12:\"current_item\";a:0:{}s:5:\"items\";a:60:{i:0;a:6:{s:5:\"title\";s:33:\"Owen Winkler: Role Manager Plugin\";s:4:\"guid\";s:60:\"http://asymptomatic.net/2005/12/31/2189/role-manager-plugin/\";s:4:\"link\";s:60:\"http://asymptomatic.net/2005/12/31/2189/role-manager-plugin/\";s:11:\"description\";s:1272:\"

    I’ve noticed a strange upshoot in new and updated plugins released around the WordPress 2.0 milestone. That’s probably a good thing.

    \n

    Something that was conspicuously left out of the WordPress core (yes, it was on purpose) was a way to manage Roles as an administrator. Well, add this Role Manager plugin to the list of new WordPress 2.0 plugins.

    \n

    Note that this plugin does not really do anything but manage roles. If your blog has no need to maintain Roles (for example, if you’re the only author) then you don’t need this plugin. But if you have plugins that depend on capability levels, or you want to manage groups of user permissions, then this will allow you to do that.

    \n

    More details about how the plugin works and what you can do with it are available on the documentation site. This includes not just the admin user interface, but also instructions for how your plugin can cleanly add new capabilities to WordPress that the Role Manager will display in its interface.

    \n

    Many thanks to David House who helped with a substantial bit of coding on this one.\n

    \";s:7:\"pubdate\";s:31:\"Sat, 31 Dec 2005 04:38:33 +0000\";s:7:\"summary\";s:1272:\"

    I’ve noticed a strange upshoot in new and updated plugins released around the WordPress 2.0 milestone. That’s probably a good thing.

    \n

    Something that was conspicuously left out of the WordPress core (yes, it was on purpose) was a way to manage Roles as an administrator. Well, add this Role Manager plugin to the list of new WordPress 2.0 plugins.

    \n

    Note that this plugin does not really do anything but manage roles. If your blog has no need to maintain Roles (for example, if you’re the only author) then you don’t need this plugin. But if you have plugins that depend on capability levels, or you want to manage groups of user permissions, then this will allow you to do that.

    \n

    More details about how the plugin works and what you can do with it are available on the documentation site. This includes not just the admin user interface, but also instructions for how your plugin can cleanly add new capabilities to WordPress that the Role Manager will display in its interface.

    \n

    Many thanks to David House who helped with a substantial bit of coding on this one.\n

    \";}i:1;a:6:{s:5:\"title\";s:31:\"Mike Little: WordPress 2.0 Duke\";s:4:\"guid\";s:66:\"http://zed1.com/journalized/archives/2005/12/31/wordpress-20-duke/\";s:4:\"link\";s:66:\"http://zed1.com/journalized/archives/2005/12/31/wordpress-20-duke/\";s:11:\"description\";s:3576:\"

    Although it has been available for a few days now, WordPress 2.0 ‘Duke’ is now official.

    \n

    In fact has already had been downloaded more than 33,000 times as I write this! You can download it from the revamped WordPress.org web site.

    \n

    There is a lot of new code in this release, most of which is concerned with either the Administration interface, especially the WYSIWYG write interface, or under the hood stuff which will benefit plugin and theme developers. There are a whole bunch of bug fixes too.

    \n

    There are some very compelling reasons to upgrade to this new version, but there are a whole bunch of reasons to hold off too:

    \n

    The biggest bug-bear for me is that the Write Post page is no longer usable if you cannot use a mouse. At least, out of the box it isn’t usable (you cannot tab out of the text area for the post). If you cannot use a mouse and have to use the keyboard for input, then you need to turn off the WYSIWYG editor. Tab to the Users page, uncheck the “Use the visual rich editor when writing” checkbox, and update your profile.

    \n

    Once you have done that you can at least write and publish plain posts. Alas you still cannot upload images or other media with the new interface. That still requires the use of a mouse.

    \n

    I’m a strong believer in accessibility in applications, be they web- or desktop-based. I think computers, and especially computers in combination with the Internet, are a great leveller. Disabled people who perhaps cannot function too well in the physical world have a much more level playing field in the online world. Or at least that’s the theory.

    \n

    WordPress 1.5.2 is a reasonably accessible application (albeit not too user friendly for those who cannot use a mouse). More by virtue of it being pretty standards compliant than by design. WordPress 2.0 is no longer accessible.

    \n

    By adding a JavaScript based Visual Editor and a bunch of sexy looking draggable, expandable controls, the application has been rendered unusable in its single most important function: posting to your blog!

    \n

    It’s a real shame, because the improvements in WordPress 2.0 are many, and some of them, like the post preview now using your theme, are killer. The new user roles and capabilities too are a great new idea and will lead to some good functionality for multi-author and community blogs.

    \n

    The way uploaded images and other media are handled is much more sophisticated now: they are sub posts and can thus be displayed separately from the post which contains them and have their own comments.

    \n

    Another reason to be cautious about upgrading is that some of your plugins may no longer be compatible. There are a whole bunch that have been checked against 2.0, but there are a whole lot more which haven’t.

    \n

    In conclusion, if you are thinking about upgrading to 2.0, make sure you create a backup first. Turn off all your plugins, and even switch back to the default theme. Then upgrade and turn on your plugins one at a time. Checking nothing breaks after each one. Finally switch back to your theme and have a final check.

    \";s:7:\"pubdate\";s:31:\"Sat, 31 Dec 2005 02:17:54 +0000\";s:7:\"summary\";s:3576:\"

    Although it has been available for a few days now, WordPress 2.0 ‘Duke’ is now official.

    \n

    In fact has already had been downloaded more than 33,000 times as I write this! You can download it from the revamped WordPress.org web site.

    \n

    There is a lot of new code in this release, most of which is concerned with either the Administration interface, especially the WYSIWYG write interface, or under the hood stuff which will benefit plugin and theme developers. There are a whole bunch of bug fixes too.

    \n

    There are some very compelling reasons to upgrade to this new version, but there are a whole bunch of reasons to hold off too:

    \n

    The biggest bug-bear for me is that the Write Post page is no longer usable if you cannot use a mouse. At least, out of the box it isn’t usable (you cannot tab out of the text area for the post). If you cannot use a mouse and have to use the keyboard for input, then you need to turn off the WYSIWYG editor. Tab to the Users page, uncheck the “Use the visual rich editor when writing” checkbox, and update your profile.

    \n

    Once you have done that you can at least write and publish plain posts. Alas you still cannot upload images or other media with the new interface. That still requires the use of a mouse.

    \n

    I’m a strong believer in accessibility in applications, be they web- or desktop-based. I think computers, and especially computers in combination with the Internet, are a great leveller. Disabled people who perhaps cannot function too well in the physical world have a much more level playing field in the online world. Or at least that’s the theory.

    \n

    WordPress 1.5.2 is a reasonably accessible application (albeit not too user friendly for those who cannot use a mouse). More by virtue of it being pretty standards compliant than by design. WordPress 2.0 is no longer accessible.

    \n

    By adding a JavaScript based Visual Editor and a bunch of sexy looking draggable, expandable controls, the application has been rendered unusable in its single most important function: posting to your blog!

    \n

    It’s a real shame, because the improvements in WordPress 2.0 are many, and some of them, like the post preview now using your theme, are killer. The new user roles and capabilities too are a great new idea and will lead to some good functionality for multi-author and community blogs.

    \n

    The way uploaded images and other media are handled is much more sophisticated now: they are sub posts and can thus be displayed separately from the post which contains them and have their own comments.

    \n

    Another reason to be cautious about upgrading is that some of your plugins may no longer be compatible. There are a whole bunch that have been checked against 2.0, but there are a whole lot more which haven’t.

    \n

    In conclusion, if you are thinking about upgrading to 2.0, make sure you create a backup first. Turn off all your plugins, and even switch back to the default theme. Then upgrade and turn on your plugins one at a time. Checking nothing breaks after each one. Finally switch back to your theme and have a final check.

    \";}i:2;a:6:{s:5:\"title\";s:21:\"Dev Blog: WordPress 2\";s:4:\"guid\";s:45:\"http://wordpress.org/development/2005/12/wp2/\";s:4:\"link\";s:45:\"http://wordpress.org/development/2005/12/wp2/\";s:11:\"description\";s:8670:\"

    The WordPress community is very proud to present the next generation of WordPress to the world, our 2.0 “Duke” release, named in honor of jazz pianist and composer Duke Ellington. We’ve been working long and hard to bring you this release, and I hope you enjoy using it as much as we’e enjoyed working on it. In this release we’ve focused a tremendous amount on what we believe to be the core of blogging — the writing interface. Before you upgrade from an earlier version, remember that this is a major release and thousands of lines of code have changed. Before upgrading it’s always good, just in case, to make a backup of your database and WordPress files. It only takes a few minutes and gives you a total safety net if for whatever reason things don’t work. It is also probably a good idea to turn off your plugins, and activate them one-by-one after you’ve upgraded. Without further ado, you can download WordPress 2 right now. Read on for more information about what we think you’ll love about Duke.

    \n

    \n

    User Features

    \n
      \n
    • Completely Redesigned Backend — The first thing you’ll notice when you login to your blog is the backend has been completely overhauled for both aesthetics and usability. This is the first iteration of exciting things to come from the Shuttle team of designers that has been volunteering their time, and look for even more aesthetic improvements in the future.
    • \n
    • Faster Administration — Call it AJAX, call it DHTML, call it Larry, but we’ve paid close attention to streamlining some of the most common tasks in managing your blog. For example if you’re writing a post and you can add categories on the fly, much like tagging in Flickr. Also instead of having two separate UIs for “simple” and “advanced” posting, we’ve combined them and let you customize the layout of the page on the fly by dragging and dropping the dialogs around. It saves where you put things so when you return it’s just like you left it. When you delete a comment or category it will fade out without a page load.
    • \n
    • WYSIWYG Editing — WP dev Andy Skelton and the TinyMCE team have done a tremendous amount of work to bring a smooth WYSIWYG editing experience to WordPress. As code purists, we are very picky about what kind of HTML is generated, and while it’s not perfect yet (for instance nested lists can cause trouble) for 95% of what you do post-to-post the WYSIWYG should save you time. And if it doesn’t, you can turn it off on your profile page. One note: Safari and older versions of Opera, both fantastic browsers, don’t yet support everything that’s needed to do WYSIWYG, but we fully expect new versions of those browsers will continue to improve their standards support, so it may just be a matter of time.
    • \n
    • Included Spam and Backup Plugins — We’ve included two of the most popular WordPress plugins: Skippy’s DB backup can backup your database to a file and optionally email you a copy; Akismet is a distributed anti-spam system which gets smarter the more people use it.
    • \n
    • Resizable Editing — This is one of my personal favorite features. Ever been writing a post and that textarea seemed a little small? Happens to me all the time, and our new rich text editor includes a feature that lets you resize the editor on-the-fly by clicking on the corner, just like a regular window.
    • \n
    • Inline Uploading — We’ve optimized our uploader for image, audio, and video files and put it inline with the posting screen. You don’t have to bounce around any more when writing a post! It also will organize your files for you as you upload them to make them easier to find later. On the backend, each uploaded file is actually a “sub-post” so it can have individual comments and pingbacks, its own permalink, and even a custom template based on what type of file it is. You can click on attached files to get a menu of options, or if you’re on Firefox you can drag and drop them into your WYSIWYG editor.
    • \n
    • Faster Posting — In the past if you were linking to a number of posts or pinging a lot of update services, your posting time could appear to slow to a crawl even though everything was instantly done on the backend. We’ve modified how this works now so posting should be near-instantaneous, like everything else in WordPress.
    • \n
    • Post Preview — Another enhancement to the post screen, now when you save a post it shows a live preview of how the post would look on your site, with the stylesheet and theme and everything. No more publishing a post just to see if it works.
    • \n
    • Streamlined Importing — We’ve rewritten our import system from the ground up to be much easier to use (you no longer have to edit files), put it behind authentication, and also made it easy for new importers to be dropped into the system, much like plugins.
    • \n
    • User Roles — We had a ton of feedback on our old numerical user level system. No one was exactly sure what those numbers meant! We’ve distilled the basic functions into a set of roles — such as administrator, editor, contributor — that make it easier to understand what sort of capabilities you’re giving your blog’s users. The new system is completely pluggable too, so plugins can modify roles and create groups that have access to certain things.
    • \n
    • Header Customization — If you’re tired of the blue header in the default theme, you can now change the colors and text of it, which we’ve included as a demo of some of the new features available to theme authors.
    • \n
    \n

    Developer Features

    \n

    On the backend we’ve done a ton of changes to clean up code, make things more consistent, and enable a lot of new types of applications to be built on top of WordPress.

    \n
      \n
    • User Level Options — You can now store options on a per-user level rather than having them apply for the entire blog. An example of this in WP2 is with the rich text editor, which can be turned on or off per a user’s discretion.
    • \n
    • Improved Abstraction — We’ve eliminated almost all direct SQL queries from the code and moved them to functions and classes that make the entire program more consistent.
    • \n
    • Built-in Caching — WordPress now includes a completely pluggable object cache system that cuts the number of queries most pages do in half. By default it is disk-based, but there is already a plugin to use memcached and we expect more are on their way. We’ve only begun to tap into this.
    • \n
    • Plugin Hooks Galore — We’ve added hooks for plugin authors wherever we could think to, so what you’re able to do in the new system is pretty dramatic. Ne features like the WYSIWYG and the inline uploader are completely pluggable and can be replaced entirely.
    • \n
    • Import Framework — The new import framework allows you to create an importer with about a third of the code you used to need, and it can have a consistent interface with no extra work.
    • \n
    • Theme Functions — Themes can now include a functions.php file that will now be loaded like a plugin attached to the theme.
    • \n
    • Theme preview images — You can now include a screenshot of the theme with the download so in the WP interface your users will see a quick preview of what it looks like.
    • \n
    • Hundreds and Hundreds of Bug Fixes2.0 has hundreds of tracked bugs and enhancements, many that are very subtle.
    • \n
    \n

    You may have noticed our design has changed quite a bit. We’ve also moved WordPress.org to a newer, faster server. There were a few issues with the move which is why we’ve held off for a few days on announcing 2.0. Everything seems to be smooth sailing now.\n

    \";s:7:\"pubdate\";s:31:\"Sat, 31 Dec 2005 00:47:10 +0000\";s:7:\"summary\";s:8670:\"

    The WordPress community is very proud to present the next generation of WordPress to the world, our 2.0 “Duke” release, named in honor of jazz pianist and composer Duke Ellington. We’ve been working long and hard to bring you this release, and I hope you enjoy using it as much as we’e enjoyed working on it. In this release we’ve focused a tremendous amount on what we believe to be the core of blogging — the writing interface. Before you upgrade from an earlier version, remember that this is a major release and thousands of lines of code have changed. Before upgrading it’s always good, just in case, to make a backup of your database and WordPress files. It only takes a few minutes and gives you a total safety net if for whatever reason things don’t work. It is also probably a good idea to turn off your plugins, and activate them one-by-one after you’ve upgraded. Without further ado, you can download WordPress 2 right now. Read on for more information about what we think you’ll love about Duke.

    \n

    \n

    User Features

    \n
      \n
    • Completely Redesigned Backend — The first thing you’ll notice when you login to your blog is the backend has been completely overhauled for both aesthetics and usability. This is the first iteration of exciting things to come from the Shuttle team of designers that has been volunteering their time, and look for even more aesthetic improvements in the future.
    • \n
    • Faster Administration — Call it AJAX, call it DHTML, call it Larry, but we’ve paid close attention to streamlining some of the most common tasks in managing your blog. For example if you’re writing a post and you can add categories on the fly, much like tagging in Flickr. Also instead of having two separate UIs for “simple” and “advanced” posting, we’ve combined them and let you customize the layout of the page on the fly by dragging and dropping the dialogs around. It saves where you put things so when you return it’s just like you left it. When you delete a comment or category it will fade out without a page load.
    • \n
    • WYSIWYG Editing — WP dev Andy Skelton and the TinyMCE team have done a tremendous amount of work to bring a smooth WYSIWYG editing experience to WordPress. As code purists, we are very picky about what kind of HTML is generated, and while it’s not perfect yet (for instance nested lists can cause trouble) for 95% of what you do post-to-post the WYSIWYG should save you time. And if it doesn’t, you can turn it off on your profile page. One note: Safari and older versions of Opera, both fantastic browsers, don’t yet support everything that’s needed to do WYSIWYG, but we fully expect new versions of those browsers will continue to improve their standards support, so it may just be a matter of time.
    • \n
    • Included Spam and Backup Plugins — We’ve included two of the most popular WordPress plugins: Skippy’s DB backup can backup your database to a file and optionally email you a copy; Akismet is a distributed anti-spam system which gets smarter the more people use it.
    • \n
    • Resizable Editing — This is one of my personal favorite features. Ever been writing a post and that textarea seemed a little small? Happens to me all the time, and our new rich text editor includes a feature that lets you resize the editor on-the-fly by clicking on the corner, just like a regular window.
    • \n
    • Inline Uploading — We’ve optimized our uploader for image, audio, and video files and put it inline with the posting screen. You don’t have to bounce around any more when writing a post! It also will organize your files for you as you upload them to make them easier to find later. On the backend, each uploaded file is actually a “sub-post” so it can have individual comments and pingbacks, its own permalink, and even a custom template based on what type of file it is. You can click on attached files to get a menu of options, or if you’re on Firefox you can drag and drop them into your WYSIWYG editor.
    • \n
    • Faster Posting — In the past if you were linking to a number of posts or pinging a lot of update services, your posting time could appear to slow to a crawl even though everything was instantly done on the backend. We’ve modified how this works now so posting should be near-instantaneous, like everything else in WordPress.
    • \n
    • Post Preview — Another enhancement to the post screen, now when you save a post it shows a live preview of how the post would look on your site, with the stylesheet and theme and everything. No more publishing a post just to see if it works.
    • \n
    • Streamlined Importing — We’ve rewritten our import system from the ground up to be much easier to use (you no longer have to edit files), put it behind authentication, and also made it easy for new importers to be dropped into the system, much like plugins.
    • \n
    • User Roles — We had a ton of feedback on our old numerical user level system. No one was exactly sure what those numbers meant! We’ve distilled the basic functions into a set of roles — such as administrator, editor, contributor — that make it easier to understand what sort of capabilities you’re giving your blog’s users. The new system is completely pluggable too, so plugins can modify roles and create groups that have access to certain things.
    • \n
    • Header Customization — If you’re tired of the blue header in the default theme, you can now change the colors and text of it, which we’ve included as a demo of some of the new features available to theme authors.
    • \n
    \n

    Developer Features

    \n

    On the backend we’ve done a ton of changes to clean up code, make things more consistent, and enable a lot of new types of applications to be built on top of WordPress.

    \n
      \n
    • User Level Options — You can now store options on a per-user level rather than having them apply for the entire blog. An example of this in WP2 is with the rich text editor, which can be turned on or off per a user’s discretion.
    • \n
    • Improved Abstraction — We’ve eliminated almost all direct SQL queries from the code and moved them to functions and classes that make the entire program more consistent.
    • \n
    • Built-in Caching — WordPress now includes a completely pluggable object cache system that cuts the number of queries most pages do in half. By default it is disk-based, but there is already a plugin to use memcached and we expect more are on their way. We’ve only begun to tap into this.
    • \n
    • Plugin Hooks Galore — We’ve added hooks for plugin authors wherever we could think to, so what you’re able to do in the new system is pretty dramatic. Ne features like the WYSIWYG and the inline uploader are completely pluggable and can be replaced entirely.
    • \n
    • Import Framework — The new import framework allows you to create an importer with about a third of the code you used to need, and it can have a consistent interface with no extra work.
    • \n
    • Theme Functions — Themes can now include a functions.php file that will now be loaded like a plugin attached to the theme.
    • \n
    • Theme preview images — You can now include a screenshot of the theme with the download so in the WP interface your users will see a quick preview of what it looks like.
    • \n
    • Hundreds and Hundreds of Bug Fixes2.0 has hundreds of tracked bugs and enhancements, many that are very subtle.
    • \n
    \n

    You may have noticed our design has changed quite a bit. We’ve also moved WordPress.org to a newer, faster server. There were a few issues with the move which is why we’ve held off for a few days on announcing 2.0. Everything seems to be smooth sailing now.\n

    \";}i:3;a:6:{s:5:\"title\";s:49:\"Owen Winkler: Not installing WordPress 2.0? Why?\";s:4:\"guid\";s:72:\"http://asymptomatic.net/2005/12/30/2188/not-installing-wordpress-20-why/\";s:4:\"link\";s:72:\"http://asymptomatic.net/2005/12/30/2188/not-installing-wordpress-20-why/\";s:11:\"description\";s:1225:\"

    I’ve noticed that a few people aren’t installing WordPress 2.0. Rather, some folks have tried to install it and decided that it wasn’t ready for them yet. Still other people have decided that 2.0 doesn’t offer them anything beyond what they were getting in the 1.5 code beyond an upgrade headache.

    \n

    I’ve found a few posts online [1, 2, 3, 4] that talk about the problems they’ve had with 2.0 that have caused them to hold off. But rather than dredge up those posts, I figured I would ask directly.

    \n

    Why aren’t you upgrading?

    \n

    I would specifically like to hear from folks who have tried to upgrade and then decided against it for some reason or other.

    \n

    Please… Reasonable objections. I don’t want to get into a fight, I just want to understand why. Perhaps the community can help work out those issues.\n

    \";s:7:\"pubdate\";s:31:\"Fri, 30 Dec 2005 20:15:12 +0000\";s:7:\"summary\";s:1225:\"

    I’ve noticed that a few people aren’t installing WordPress 2.0. Rather, some folks have tried to install it and decided that it wasn’t ready for them yet. Still other people have decided that 2.0 doesn’t offer them anything beyond what they were getting in the 1.5 code beyond an upgrade headache.

    \n

    I’ve found a few posts online [1, 2, 3, 4] that talk about the problems they’ve had with 2.0 that have caused them to hold off. But rather than dredge up those posts, I figured I would ask directly.

    \n

    Why aren’t you upgrading?

    \n

    I would specifically like to hear from folks who have tried to upgrade and then decided against it for some reason or other.

    \n

    Please… Reasonable objections. I don’t want to get into a fight, I just want to understand why. Perhaps the community can help work out those issues.\n

    \";}i:4;a:6:{s:5:\"title\";s:57:\"Alex King: WordPress 2.0 Tooltips are Wrong for Mac Users\";s:4:\"guid\";s:63:\"http://www.alexking.org/blog/2005/12/30/wordpress-rte-tooltips/\";s:4:\"link\";s:63:\"http://www.alexking.org/blog/2005/12/30/wordpress-rte-tooltips/\";s:11:\"description\";s:913:\"

    I’m waiting for the inevitable .1 release before upgrading, but I’ve started testing WordPress 2.0. One of the first things I noticed was that the tooltips on the new rich text editor are just plain wrong for Mac users - we gots no “Alt” key, yo!

    \n

    Mentally replace “Alt” with “Ctrl” and you’ll be fine. I guess we know what platform Matt and company develop on. \":)\"

    \n

    I created a bug in Trac and I added a task for myself to create a patch for this (if no one else has), but probably won’t get to it until after January 16th at the earliest.\n

    \";s:7:\"pubdate\";s:31:\"Fri, 30 Dec 2005 18:17:42 +0000\";s:7:\"summary\";s:913:\"

    I’m waiting for the inevitable .1 release before upgrading, but I’ve started testing WordPress 2.0. One of the first things I noticed was that the tooltips on the new rich text editor are just plain wrong for Mac users - we gots no “Alt” key, yo!

    \n

    Mentally replace “Alt” with “Ctrl” and you’ll be fine. I guess we know what platform Matt and company develop on. \":)\"

    \n

    I created a bug in Trac and I added a task for myself to create a patch for this (if no one else has), but probably won’t get to it until after January 16th at the earliest.\n

    \";}i:5;a:6:{s:5:\"title\";s:74:\"Weblog Tools Collection: Wordpress Plugins for the Admin Side of your Blog\";s:4:\"guid\";s:103:\"http://weblogtoolscollection.com/archives/2005/12/30/wordpress-plugins-for-the-admin-side-of-your-blog/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=554\";s:11:\"description\";s:2808:\"

    For those who are not completely happy with the usability or the look and feel of the back office in their blog, there are a few plugins around, compatible with or made for Wordpress 2.0, that rework or enhance the admin area. I’ll name :

    \n
      \n
    • Paged Comment Editing
      \nBy default, “Manage Comments” only list the 20 last comments. What if you were on offline holidays for a month and have a blog that receives quite a few feedback ? This plugin allows browsing of older comments with paging through them.
    • \n
    • Cat 2 Tag
      \nIf you create a lot of categories, this plugin makes selection easier when writing a post : a handy “tag cloud” avoids cumbersome scrolling through a long category list. There is also a fancy “suggest category” feature that proposes categories as you type them.
    • \n
    • Wordpress Admin Drop Down Menu
      \nThe lazy and the productive will love it : you can go from any to any admin page without having to stop by the “top level” page first. For instance, no more loading “Manage” first , and then “Comments”, since all admin links are available in a CSS driven drop down menu (demo). Admin menu the way it was meant to be (and even compatible with Tiger Admin)
    • \n
    • Tiger Admin
      \nWhile adding no particular feature, this plugin completely revamp the admin part of your blog by heavily tweaking the CSS, giving your admin pages a brand new look (for Safari, Camino and Firefox only)
    • \n
    \n

    If you are using a plugin that adds functionnalities or redesigns the admin interface, feel free to pimp it in the comments. Also, I advise plugin authors to update their plugin descriptions over at WP-Plugins.net once the administration pages will include “Wordpress 2.0″ in the compatibility check list.\n

    \nTechnorati Tags: css design wordpress wordpress 2 0 wordpress admin wordpress plugin\";s:7:\"pubdate\";s:31:\"Fri, 30 Dec 2005 17:00:16 +0000\";s:7:\"summary\";s:2808:\"

    For those who are not completely happy with the usability or the look and feel of the back office in their blog, there are a few plugins around, compatible with or made for Wordpress 2.0, that rework or enhance the admin area. I’ll name :

    \n
      \n
    • Paged Comment Editing
      \nBy default, “Manage Comments” only list the 20 last comments. What if you were on offline holidays for a month and have a blog that receives quite a few feedback ? This plugin allows browsing of older comments with paging through them.
    • \n
    • Cat 2 Tag
      \nIf you create a lot of categories, this plugin makes selection easier when writing a post : a handy “tag cloud” avoids cumbersome scrolling through a long category list. There is also a fancy “suggest category” feature that proposes categories as you type them.
    • \n
    • Wordpress Admin Drop Down Menu
      \nThe lazy and the productive will love it : you can go from any to any admin page without having to stop by the “top level” page first. For instance, no more loading “Manage” first , and then “Comments”, since all admin links are available in a CSS driven drop down menu (demo). Admin menu the way it was meant to be (and even compatible with Tiger Admin)
    • \n
    • Tiger Admin
      \nWhile adding no particular feature, this plugin completely revamp the admin part of your blog by heavily tweaking the CSS, giving your admin pages a brand new look (for Safari, Camino and Firefox only)
    • \n
    \n

    If you are using a plugin that adds functionnalities or redesigns the admin interface, feel free to pimp it in the comments. Also, I advise plugin authors to update their plugin descriptions over at WP-Plugins.net once the administration pages will include “Wordpress 2.0″ in the compatibility check list.\n

    \nTechnorati Tags: css design wordpress wordpress 2 0 wordpress admin wordpress plugin\";}i:6;a:6:{s:5:\"title\";s:53:\"Owen Winkler: Wordpress Plugin : Admin Drop Down Menu\";s:4:\"guid\";s:78:\"http://asymptomatic.net/2005/12/30/2187/wordpress-plugin-admin-drop-down-menu/\";s:4:\"link\";s:78:\"http://asymptomatic.net/2005/12/30/2187/wordpress-plugin-admin-drop-down-menu/\";s:11:\"description\";s:545:\"

    Ozh’s Admin Drop Down Menu Plugin for WordPress is something that I’ve been waiting for someone else to write for a long time. If you’re not using this, then you’re certainly not a power user.

    \n

    I don’t often write about other people’s plugins, but this one is so simple and essential in must be made more public.

    \n

    Thanks so much for this!

    \";s:7:\"pubdate\";s:31:\"Fri, 30 Dec 2005 16:36:58 +0000\";s:7:\"summary\";s:545:\"

    Ozh’s Admin Drop Down Menu Plugin for WordPress is something that I’ve been waiting for someone else to write for a long time. If you’re not using this, then you’re certainly not a power user.

    \n

    I don’t often write about other people’s plugins, but this one is so simple and essential in must be made more public.

    \n

    Thanks so much for this!

    \";}i:7;a:6:{s:5:\"title\";s:30:\"Matt: One Million Spam Blocked\";s:4:\"guid\";s:49:\"http://photomatt.net/2005/12/30/millions-blocked/\";s:4:\"link\";s:49:\"http://photomatt.net/2005/12/30/millions-blocked/\";s:11:\"description\";s:120:\"

    Akismet has blocked over one million spam already.\n

    \";s:7:\"pubdate\";s:31:\"Fri, 30 Dec 2005 15:32:32 +0000\";s:7:\"summary\";s:120:\"

    Akismet has blocked over one million spam already.\n

    \";}i:8;a:6:{s:5:\"title\";s:51:\"Weblog Tools Collection: Blogging Wishlist for 2006\";s:4:\"guid\";s:80:\"http://weblogtoolscollection.com/archives/2005/12/30/blogging-wishlist-for-2006/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=553\";s:11:\"description\";s:3062:\"

    Yes, I lifted the idea from another blog since their focus was somewhere else.

    \n

    Here is my blogging wishlist for 2006. Tell us what you wish for or trackback this post from your blog.

    \n
      \n
    • \nSince this is going to be the year of the aggregator (FeedDemon, Dave Winer’s effort, MSN, Google … ) I would like to find, try and settle on (maybe pay a small monthly fee) an aggregator that makes me feel as much at home as the Wordpress Write page.\n
    • \n
    • \nI would like to see the search engines treat splogs worse. Period.\n
    • \n
    • \nIn addition, I would like to see the blogging world treat plagiarism with scorn. I would like to have more transparency and less dishonesty in popular “moneymaking” content. Search engines could also play an important role in this.\n
    • \n
    • \nSince blogging is already popular, I would like to see corporations using existing blogs to leverage sales and tweak their offerings to make bloggers happy. Hint: Search for your product on the web, find bloggers who talk about your product, make these bloggers happy and your product will get free and positive advertising.\n
    • \n
    • \nI would like to see bloggers make more profit from their blogs. Adsense is already very viable and many bloggers are already making a killing, but there is more money to go around. Advertising and profit sharing with bloggers would make casual bloggers very happy. Happy bloggers == better content == better blogosphere.\n
    • \n
    • \nI was wowed by Flickr and Memorandum. I would really like to see more innovative apps that focus on the blogging world and the social network that is blogging.\n
    • \n
    • \nPodcasting is nice in its own arena, but I just dont think I want every blog to become a podcast. Yet, I crave multimedia in blogging. I would really like to be surprised.\n
    • \n
    • \nMSN Spaces scare me. Asian blog networks are even worse. Not sure what I wish for them, but I have yet to read a single blog on any of those networks that I come back to.\n
    • \n
    • \nLess network, more blog.\n
    • \n
    • \nI really would like to see Wordpress do better, even better than it already is. This is a personal wish. I am not sure why I have this affinity towards Wordpress et al, but I do.\n
    • \n
    • \nAkismet is just frickin wonderful. This time next year, I hope to be deleting even lesser spam than I do today.\n
    • \n
    • \nYahoo! has the right idea when they added Wordpress to their hosting lineup. I would like to see larger financial players help out and spread the wealth among Open Source initiatives in the blogging world and beyond. Free work for OSS is nice, but a little money to ice the cake is always just that much better.\n
    • \n
    • \nFinally, I would like to see more and better content. Well written personal blogs are refreshing reads.\n
    • \n
    \nTechnorati Tags: blogging wishlist for 2006\";s:7:\"pubdate\";s:31:\"Fri, 30 Dec 2005 11:51:49 +0000\";s:7:\"summary\";s:3062:\"

    Yes, I lifted the idea from another blog since their focus was somewhere else.

    \n

    Here is my blogging wishlist for 2006. Tell us what you wish for or trackback this post from your blog.

    \n
      \n
    • \nSince this is going to be the year of the aggregator (FeedDemon, Dave Winer’s effort, MSN, Google … ) I would like to find, try and settle on (maybe pay a small monthly fee) an aggregator that makes me feel as much at home as the Wordpress Write page.\n
    • \n
    • \nI would like to see the search engines treat splogs worse. Period.\n
    • \n
    • \nIn addition, I would like to see the blogging world treat plagiarism with scorn. I would like to have more transparency and less dishonesty in popular “moneymaking” content. Search engines could also play an important role in this.\n
    • \n
    • \nSince blogging is already popular, I would like to see corporations using existing blogs to leverage sales and tweak their offerings to make bloggers happy. Hint: Search for your product on the web, find bloggers who talk about your product, make these bloggers happy and your product will get free and positive advertising.\n
    • \n
    • \nI would like to see bloggers make more profit from their blogs. Adsense is already very viable and many bloggers are already making a killing, but there is more money to go around. Advertising and profit sharing with bloggers would make casual bloggers very happy. Happy bloggers == better content == better blogosphere.\n
    • \n
    • \nI was wowed by Flickr and Memorandum. I would really like to see more innovative apps that focus on the blogging world and the social network that is blogging.\n
    • \n
    • \nPodcasting is nice in its own arena, but I just dont think I want every blog to become a podcast. Yet, I crave multimedia in blogging. I would really like to be surprised.\n
    • \n
    • \nMSN Spaces scare me. Asian blog networks are even worse. Not sure what I wish for them, but I have yet to read a single blog on any of those networks that I come back to.\n
    • \n
    • \nLess network, more blog.\n
    • \n
    • \nI really would like to see Wordpress do better, even better than it already is. This is a personal wish. I am not sure why I have this affinity towards Wordpress et al, but I do.\n
    • \n
    • \nAkismet is just frickin wonderful. This time next year, I hope to be deleting even lesser spam than I do today.\n
    • \n
    • \nYahoo! has the right idea when they added Wordpress to their hosting lineup. I would like to see larger financial players help out and spread the wealth among Open Source initiatives in the blogging world and beyond. Free work for OSS is nice, but a little money to ice the cake is always just that much better.\n
    • \n
    • \nFinally, I would like to see more and better content. Well written personal blogs are refreshing reads.\n
    • \n
    \nTechnorati Tags: blogging wishlist for 2006\";}i:9;a:6:{s:5:\"title\";s:19:\"Matt: In Cincinnati\";s:4:\"guid\";s:46:\"http://photomatt.net/2005/12/29/in-cincinnati/\";s:4:\"link\";s:46:\"http://photomatt.net/2005/12/29/in-cincinnati/\";s:11:\"description\";s:753:\"

    I’m in the Cincinnati airport, and they just announced an emergency. All of the stores have closed and the halls are empty, everyone has proceeded to the nearest exit. Except the people around me. I’m on a flight to New York, and apparently the blinking lights and alarms don’t don’t phase these folks. The desk attendent just announced they’re boarding zones 2 and 3. Okay, they just announced my zone, see you guys on the flip side. Update from Blacberry: Right after I got on the plane they made all those jaded New Yorkers evacuate like everyone else and they’ve stopped people from boarding the plane. Update 2: They seem to have fixed things and people are boarding again. I wonder what the problem was.\n

    \";s:7:\"pubdate\";s:31:\"Thu, 29 Dec 2005 23:52:03 +0000\";s:7:\"summary\";s:753:\"

    I’m in the Cincinnati airport, and they just announced an emergency. All of the stores have closed and the halls are empty, everyone has proceeded to the nearest exit. Except the people around me. I’m on a flight to New York, and apparently the blinking lights and alarms don’t don’t phase these folks. The desk attendent just announced they’re boarding zones 2 and 3. Okay, they just announced my zone, see you guys on the flip side. Update from Blacberry: Right after I got on the plane they made all those jaded New Yorkers evacuate like everyone else and they’ve stopped people from boarding the plane. Update 2: They seem to have fixed things and people are boarding again. I wonder what the problem was.\n

    \";}i:10;a:6:{s:5:\"title\";s:49:\"Weblog Tools Collection: WP Theme: SAartsEmerging\";s:4:\"guid\";s:77:\"http://weblogtoolscollection.com/archives/2005/12/29/wp-theme-saartsemerging/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=552\";s:11:\"description\";s:407:\"

    WP Theme: SAartsEmerging2 column, artsy theme. WordPress 2.0 Compatible, customizable through included p-shop file, CSS and XHTML valid.

    \nTechnorati Tags: SAartsEmerging wordpress theme\";s:7:\"pubdate\";s:31:\"Thu, 29 Dec 2005 10:29:05 +0000\";s:7:\"summary\";s:407:\"

    WP Theme: SAartsEmerging2 column, artsy theme. WordPress 2.0 Compatible, customizable through included p-shop file, CSS and XHTML valid.

    \nTechnorati Tags: SAartsEmerging wordpress theme\";}i:11;a:6:{s:5:\"title\";s:62:\"Weblog Tools Collection: How Big A Problem Is Blog Plagiarism?\";s:4:\"guid\";s:90:\"http://weblogtoolscollection.com/archives/2005/12/28/how-big-a-problem-is-blog-plagiarism/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=551\";s:11:\"description\";s:1214:\"

    How Big A Problem Is Blog Plagiarism? I have written about this in the past and used to have very strong views about it. In the past couple of weeks I have been alerted by readers and friends of quite a few other blogs that are copying my content (among others) and publishing it as their own. The number is surprisingly large. I stand on the fence on this issue. I love this comment on the Techdirt article. I came here from memeorandum, which is another site profitting from your content. That’s how the blogosphere works. I think our view of IP in the blog world will have to go through some major upheaval (like Om’s discussion) and might even need a few big lawsuits before bloggers know how far they can go without getting making people angry.

    \nTechnorati Tags: blogging plagiarism\";s:7:\"pubdate\";s:31:\"Wed, 28 Dec 2005 15:30:17 +0000\";s:7:\"summary\";s:1214:\"

    How Big A Problem Is Blog Plagiarism? I have written about this in the past and used to have very strong views about it. In the past couple of weeks I have been alerted by readers and friends of quite a few other blogs that are copying my content (among others) and publishing it as their own. The number is surprisingly large. I stand on the fence on this issue. I love this comment on the Techdirt article. I came here from memeorandum, which is another site profitting from your content. That’s how the blogosphere works. I think our view of IP in the blog world will have to go through some major upheaval (like Om’s discussion) and might even need a few big lawsuits before bloggers know how far they can go without getting making people angry.

    \nTechnorati Tags: blogging plagiarism\";}i:12;a:6:{s:5:\"title\";s:86:\"Weblog Tools Collection: Read Most of O’Reilly’s Hacks Books for Free Using Google\";s:4:\"guid\";s:109:\"http://weblogtoolscollection.com/archives/2005/12/28/read-most-of-oreillys-hacks-books-for-free-using-google/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=550\";s:11:\"description\";s:441:\"

    Read Most of O’Reilly’s Hacks Books for Free Using Google: This tutorial is sure to make Google and O’Reilly quite unhappy.

    \nTechnorati Tags: Google Book Search O\\\'Reilly\";s:7:\"pubdate\";s:31:\"Wed, 28 Dec 2005 10:57:58 +0000\";s:7:\"summary\";s:441:\"

    Read Most of O’Reilly’s Hacks Books for Free Using Google: This tutorial is sure to make Google and O’Reilly quite unhappy.

    \nTechnorati Tags: Google Book Search O\\\'Reilly\";}i:13;a:6:{s:5:\"title\";s:49:\"Weblog Tools Collection: Wordpress 2.0 Released!!\";s:4:\"guid\";s:74:\"http://weblogtoolscollection.com/archives/2005/12/26/wordpress-20-release/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=549\";s:11:\"description\";s:163:\"

    As of this post, Wordpress 2.0 is out. Go get it!
    \nPS: Check out the new site design while you are at it!\n

    \";s:7:\"pubdate\";s:31:\"Mon, 26 Dec 2005 17:13:36 +0000\";s:7:\"summary\";s:163:\"

    As of this post, Wordpress 2.0 is out. Go get it!
    \nPS: Check out the new site design while you are at it!\n

    \";}i:14;a:6:{s:5:\"title\";s:59:\"Weblog Tools Collection: WordPress Ajax Commenting Tutorial\";s:4:\"guid\";s:88:\"http://weblogtoolscollection.com/archives/2005/12/26/wordpress-ajax-commenting-tutorial/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=548\";s:11:\"description\";s:391:\"

    WordPress Ajax Commenting Tutorial: A well rounded tutorial for adding AJAX commenting to your Wordpress theme with examples from existing templates.

    \nTechnorati Tags: wordpres ajax commenting\";s:7:\"pubdate\";s:31:\"Mon, 26 Dec 2005 16:30:17 +0000\";s:7:\"summary\";s:391:\"

    WordPress Ajax Commenting Tutorial: A well rounded tutorial for adding AJAX commenting to your Wordpress theme with examples from existing templates.

    \nTechnorati Tags: wordpres ajax commenting\";}i:15;a:6:{s:5:\"title\";s:81:\"Weblog Tools Collection: The Top 10 interesting people in the Blogosphere in 2005\";s:4:\"guid\";s:110:\"http://weblogtoolscollection.com/archives/2005/12/26/the-top-10-interesting-people-in-the-blogosphere-in-2005/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=547\";s:11:\"description\";s:473:\"

    The Top 10 interesting people in the Blogosphere in 2005: Interesting read, especially since Matt rises up and above all the others. Also of note is that blogging “networks” are dominating that list. Anyone read the Wired article on Jason? PS: Duncan, you need a better server!

    \";s:7:\"pubdate\";s:31:\"Mon, 26 Dec 2005 11:25:22 +0000\";s:7:\"summary\";s:473:\"

    The Top 10 interesting people in the Blogosphere in 2005: Interesting read, especially since Matt rises up and above all the others. Also of note is that blogging “networks” are dominating that list. Anyone read the Wired article on Jason? PS: Duncan, you need a better server!

    \";}i:16;a:6:{s:5:\"title\";s:31:\"Weblog Tools Collection: ajchat\";s:4:\"guid\";s:60:\"http://weblogtoolscollection.com/archives/2005/12/26/ajchat/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=546\";s:11:\"description\";s:643:\"

    ajchat: AJAX chatrooms, create rooms on demand, share from a website, link from a blog. I hate iframes and really dislike those little “shoutbox” things, but thats a personal preference. I do like the IRC like instant chatrooms. I wonder if there are any plans to release the code? ajchat: Try out the weblogtoolscollection chat\n

    \nTechnorati Tags: ajax ajchat\";s:7:\"pubdate\";s:31:\"Mon, 26 Dec 2005 11:03:20 +0000\";s:7:\"summary\";s:643:\"

    ajchat: AJAX chatrooms, create rooms on demand, share from a website, link from a blog. I hate iframes and really dislike those little “shoutbox” things, but thats a personal preference. I do like the IRC like instant chatrooms. I wonder if there are any plans to release the code? ajchat: Try out the weblogtoolscollection chat\n

    \nTechnorati Tags: ajax ajchat\";}i:17;a:6:{s:5:\"title\";s:46:\"Weblog Tools Collection: WP Theme: WPAndreas03\";s:4:\"guid\";s:74:\"http://weblogtoolscollection.com/archives/2005/12/24/wp-theme-wpandreas03/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=545\";s:11:\"description\";s:637:\"

    WP Theme: WPAndreas03 Bold, well laid out, clean and pleasing two column theme for Wordpress. It was pointed out by Andreas of OSWD.org fame. The author of WPANdreas03 claims that he has tested the theme with the upcoming Wordpress 2.0 and it works fine.

    \nTechnorati Tags: wordpress theme wpandreas03\";s:7:\"pubdate\";s:31:\"Sat, 24 Dec 2005 09:24:20 +0000\";s:7:\"summary\";s:637:\"

    WP Theme: WPAndreas03 Bold, well laid out, clean and pleasing two column theme for Wordpress. It was pointed out by Andreas of OSWD.org fame. The author of WPANdreas03 claims that he has tested the theme with the upcoming Wordpress 2.0 and it works fine.

    \nTechnorati Tags: wordpress theme wpandreas03\";}i:18;a:6:{s:5:\"title\";s:47:\"Dougal Campbell: New features in WordPress 2.0?\";s:4:\"guid\";s:70:\"http://dougal.gunters.org/blog/2005/12/24/new-features-in-wordpress-20\";s:4:\"link\";s:70:\"http://dougal.gunters.org/blog/2005/12/24/new-features-in-wordpress-20\";s:11:\"description\";s:1522:\"

    \nA lot of people are still asking what the differences are between WordPress version 1.5.2 and version 2.0. I did mention that a lot of the differences are under the surface, and I mentioned the WYSIWYG editor and the new user permissions scheme. Rather than list the changes here, I\'ll point you to those who have already written about it.\n

    \n

    \nOne of the best articles is probably What\'s New in WordPress 2.0? over on Owen Winkler\'s site. There are also several articles over on Ryan\'s site which detail some of the new changes.\n

    \n
    \n\"Christmas\n

    Christmas Lights

    \n
    \n

    \nOne of the things that I only recently discovered myself is the fact that images uploaded to a post become \"attachments\" which become sub-pages themselves. In other words, each uploaded image becomes a WordPress page, with its own comments. This gives you the basic functionality of a photo gallery without the need for any external software. Click on the image in this post for an example.\n

    \";s:7:\"pubdate\";s:31:\"Sat, 24 Dec 2005 05:39:16 +0000\";s:7:\"summary\";s:1522:\"

    \nA lot of people are still asking what the differences are between WordPress version 1.5.2 and version 2.0. I did mention that a lot of the differences are under the surface, and I mentioned the WYSIWYG editor and the new user permissions scheme. Rather than list the changes here, I\'ll point you to those who have already written about it.\n

    \n

    \nOne of the best articles is probably What\'s New in WordPress 2.0? over on Owen Winkler\'s site. There are also several articles over on Ryan\'s site which detail some of the new changes.\n

    \n
    \n\"Christmas\n

    Christmas Lights

    \n
    \n

    \nOne of the things that I only recently discovered myself is the fact that images uploaded to a post become \"attachments\" which become sub-pages themselves. In other words, each uploaded image becomes a WordPress page, with its own comments. This gives you the basic functionality of a photo gallery without the need for any external software. Click on the image in this post for an example.\n

    \";}i:19;a:6:{s:5:\"title\";s:23:\"Ryan: Memcached Backend\";s:4:\"guid\";s:55:\"http://ryan.wordpress.com/2005/12/23/memcached-backend/\";s:4:\"link\";s:55:\"http://ryan.wordpress.com/2005/12/23/memcached-backend/\";s:11:\"description\";s:1481:\"

    Memcached is a distributed memory object caching system. WordPress 2.0 can make use of memcached by dropping in a special backend for the WP object cache. The memcached backend replaces the default backend and directs all cache requests to one or more memcached daemons. You must have a memcached daemon running somewhere for this to work. Unless you’re managing the server on which your blog is running, you probably can’t run a memcached daemon, making this backend useless to you. The memcached backend is targeted at ISPs and those running WPMU. If you are using WPMU and distributing DB requests across multiple servers, memcached will come in very handy. Using memcached for a single blog isn’t really worth it. In my tests, it was sometimes slower than using the default object cache backend.

    \n

    To install, copy object-cache.php and memcached-client.php to your wp-content/ directory.

    \n

    To configure, define $memcached_servers in wp-config.php. This is an array of host and ports to which to connect. By default, the backend attempts to connect to port 11211 on the localhost.

    \n

    Example server config:

    \n

    $memcached_servers = array(\'192.168.1.1:11211\', \'192.168.1.2:11211\');\n

    \";s:7:\"pubdate\";s:31:\"Sat, 24 Dec 2005 03:33:04 +0000\";s:7:\"summary\";s:1481:\"

    Memcached is a distributed memory object caching system. WordPress 2.0 can make use of memcached by dropping in a special backend for the WP object cache. The memcached backend replaces the default backend and directs all cache requests to one or more memcached daemons. You must have a memcached daemon running somewhere for this to work. Unless you’re managing the server on which your blog is running, you probably can’t run a memcached daemon, making this backend useless to you. The memcached backend is targeted at ISPs and those running WPMU. If you are using WPMU and distributing DB requests across multiple servers, memcached will come in very handy. Using memcached for a single blog isn’t really worth it. In my tests, it was sometimes slower than using the default object cache backend.

    \n

    To install, copy object-cache.php and memcached-client.php to your wp-content/ directory.

    \n

    To configure, define $memcached_servers in wp-config.php. This is an array of host and ports to which to connect. By default, the backend attempts to connect to port 11211 on the localhost.

    \n

    Example server config:

    \n

    $memcached_servers = array(\'192.168.1.1:11211\', \'192.168.1.2:11211\');\n

    \";}i:20;a:6:{s:5:\"title\";s:54:\"Weblog Tools Collection: Brainstorm: NetFlix for books\";s:4:\"guid\";s:82:\"http://weblogtoolscollection.com/archives/2005/12/23/brainstorm-netflix-for-books/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=544\";s:11:\"description\";s:1966:\"

    I received some books in the mail today from Intel on IA-32 Assembly language development and the sheer weight of these books reminded me that I was likely to never actually read them, let alone leaf through them when I actually needed some reference. I tend to just go to Google and search for the topic before I ever pick up a book anymore. In spite of the ease of reading stuff on the web, it is still less satisfying and intuitive (and in many ways, less informative) than actually reading the book. I like Google Print, but that is a teaser without a carrot at the end of the stick.

    \n

    I know that there are eBooks and there are a variety of other electronic publication services, but could one of the big poobahs (Amazon, Google, Microsoft etc.) come up with a searchable online library of books that could be “checked out” on a time sensitive basis. The service could be charged per book, per epoch or otherwise. I sure would get a subscription! In this age of the iTunes, RIAA and legal music downloads, I am surprised that there is such lack of interest in monetizing the online library.

    \n

    The existing services are poor and fragmented at best and they leave a bitter taste with their high prices. Not only that, buying an eBook is a little like shopping for gifts on the 23rd of December; you have to visit twenty different stores, stand in line for hours and face the wrath of bad drivers (have you noticed that everyone but me is a bad driver?) in the parking lot. No one location/site has a good selection of stuff I want.

    \n

    While they are at it, could they also come up with a better format for publishing books online?\n

    \nTechnorati Tags: ebooks legal music downloads\";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 21:30:33 +0000\";s:7:\"summary\";s:1966:\"

    I received some books in the mail today from Intel on IA-32 Assembly language development and the sheer weight of these books reminded me that I was likely to never actually read them, let alone leaf through them when I actually needed some reference. I tend to just go to Google and search for the topic before I ever pick up a book anymore. In spite of the ease of reading stuff on the web, it is still less satisfying and intuitive (and in many ways, less informative) than actually reading the book. I like Google Print, but that is a teaser without a carrot at the end of the stick.

    \n

    I know that there are eBooks and there are a variety of other electronic publication services, but could one of the big poobahs (Amazon, Google, Microsoft etc.) come up with a searchable online library of books that could be “checked out” on a time sensitive basis. The service could be charged per book, per epoch or otherwise. I sure would get a subscription! In this age of the iTunes, RIAA and legal music downloads, I am surprised that there is such lack of interest in monetizing the online library.

    \n

    The existing services are poor and fragmented at best and they leave a bitter taste with their high prices. Not only that, buying an eBook is a little like shopping for gifts on the 23rd of December; you have to visit twenty different stores, stand in line for hours and face the wrath of bad drivers (have you noticed that everyone but me is a bad driver?) in the parking lot. No one location/site has a good selection of stuff I want.

    \n

    While they are at it, could they also come up with a better format for publishing books online?\n

    \nTechnorati Tags: ebooks legal music downloads\";}i:21;a:6:{s:5:\"title\";s:44:\"Weblog Tools Collection: Lost in Translation\";s:4:\"guid\";s:73:\"http://weblogtoolscollection.com/archives/2005/12/23/lost-in-translation/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=543\";s:11:\"description\";s:124:\"

    Lost in Translation: Amazingly funny! Try “Merry Christmas!”

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 18:00:47 +0000\";s:7:\"summary\";s:124:\"

    Lost in Translation: Amazingly funny! Try “Merry Christmas!”

    \";}i:22;a:6:{s:5:\"title\";s:66:\"Weblog Tools Collection: WordPress and the Performancing Extension\";s:4:\"guid\";s:95:\"http://weblogtoolscollection.com/archives/2005/12/23/wordpress-and-the-performancing-extension/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=542\";s:11:\"description\";s:783:\"

    Wordpress and Performancing Extension for Firefox: A Dummies version of implementing the extension for WordPress. As a musing, a few years ago, “Dummies Version” would/could have offended some people, but the “…” for Dummies series of books have become so common that any tutorial labeled as “for dummies” is meant to be viewed as written simply, succintly with the most novice user in mind. [EDIT] The extension is buggy. They need to stop adding slashes for Wordpress posts!\n

    \nTechnorati Tags: performancing wordpress\";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 17:00:39 +0000\";s:7:\"summary\";s:783:\"

    Wordpress and Performancing Extension for Firefox: A Dummies version of implementing the extension for WordPress. As a musing, a few years ago, “Dummies Version” would/could have offended some people, but the “…” for Dummies series of books have become so common that any tutorial labeled as “for dummies” is meant to be viewed as written simply, succintly with the most novice user in mind. [EDIT] The extension is buggy. They need to stop adding slashes for Wordpress posts!\n

    \nTechnorati Tags: performancing wordpress\";}i:23;a:6:{s:5:\"title\";s:46:\"Weblog Tools Collection: Feed Reader Breakdown\";s:4:\"guid\";s:75:\"http://weblogtoolscollection.com/archives/2005/12/23/feed-reader-breakdown/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=541\";s:11:\"description\";s:1335:\"

    For those of you that pay extra close attention to what you read through your feedreader (or are just obsessed with looking at links and how they work) you might have noticed that I have switched the feeds for Weblogtoolscollection to Feedburner. This gives me better control over the content, asssures that the content does not get lost when and if my blog is down and gives me cool reports such as the graphic here. I do miss being able to instantly modify my feed without having to ping Feedburner over and over again.
    \n\"Feed
    \nI personally (still) use Feed on Feeds and since Alex did not offer me the chance to alpha test FeedLounge (just kidding Alex, no hard feelings at all) I have modified FoF to suit my tastes and my needs.

    \n

    As I’m sure everyone in this business is aware, the feed reader market has heated up considerably. There are new players such as Google, MSN (though still quite annoying to use) and of course the old skool players such as bloglines and Rojo. Most of these have their set of benefits, but from my traffic stats, it looks like Bloglines is winning hands down.

    \n

    What feed reader do you use?\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 17:00:39 +0000\";s:7:\"summary\";s:1335:\"

    For those of you that pay extra close attention to what you read through your feedreader (or are just obsessed with looking at links and how they work) you might have noticed that I have switched the feeds for Weblogtoolscollection to Feedburner. This gives me better control over the content, asssures that the content does not get lost when and if my blog is down and gives me cool reports such as the graphic here. I do miss being able to instantly modify my feed without having to ping Feedburner over and over again.
    \n\"Feed
    \nI personally (still) use Feed on Feeds and since Alex did not offer me the chance to alpha test FeedLounge (just kidding Alex, no hard feelings at all) I have modified FoF to suit my tastes and my needs.

    \n

    As I’m sure everyone in this business is aware, the feed reader market has heated up considerably. There are new players such as Google, MSN (though still quite annoying to use) and of course the old skool players such as bloglines and Rojo. Most of these have their set of benefits, but from my traffic stats, it looks like Bloglines is winning hands down.

    \n

    What feed reader do you use?\n

    \";}i:24;a:6:{s:5:\"title\";s:50:\"Weblog Tools Collection: One in Five Blogs Is Spam\";s:4:\"guid\";s:79:\"http://weblogtoolscollection.com/archives/2005/12/23/one-in-five-blogs-is-spam/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=540\";s:11:\"description\";s:257:\"

    One in Five Blogs Is Spam While 80,000 blogs may be created every day, about one in five is spam, according to new research. Surprised? Nah!\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 15:00:49 +0000\";s:7:\"summary\";s:257:\"

    One in Five Blogs Is Spam While 80,000 blogs may be created every day, about one in five is spam, according to new research. Surprised? Nah!\n

    \";}i:25;a:6:{s:5:\"title\";s:77:\"Weblog Tools Collection: Wordpress Theme Zip for Lazy Wordpress Theme Authors\";s:4:\"guid\";s:106:\"http://weblogtoolscollection.com/archives/2005/12/23/wordpress-theme-zip-for-lazy-wordpress-theme-authors/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=539\";s:11:\"description\";s:680:\"

    Wordpress Theme Zip: easiest (and laziest) way to generate a zip archive from a Wordpress Theme that is being worked on, so you don’t have to manually create, update and upload a new zip file every time you modify a file in it.\n

    \nTechnorati Tags: php wordpress wordpress hack wordpress theme\";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 10:15:00 +0000\";s:7:\"summary\";s:680:\"

    Wordpress Theme Zip: easiest (and laziest) way to generate a zip archive from a Wordpress Theme that is being worked on, so you don’t have to manually create, update and upload a new zip file every time you modify a file in it.\n

    \nTechnorati Tags: php wordpress wordpress hack wordpress theme\";}i:26;a:6:{s:5:\"title\";s:54:\"Owen Winkler: What’s Not Gonna Work in WordPress 2.0\";s:4:\"guid\";s:77:\"http://asymptomatic.net/2005/12/19/2171/whats-not-gonna-work-in-wordpress-20/\";s:4:\"link\";s:77:\"http://asymptomatic.net/2005/12/19/2171/whats-not-gonna-work-in-wordpress-20/\";s:11:\"description\";s:8068:\"

    The most often asked question regarding WordPress 2.0 is, “When is it coming out?” Well, that’s going to be this week.

    \n

    The second most often asked question regarding WordPress 2.0 is, “What is changed?” I think I wrote about that before.

    \n

    The third most often asked question regarding WordPress 2.0 is, “Will my plugins and/or themes work?” Uh…. Possibly?
    \n
    \nThis is also known as the dreaded question, “In what way is WordPress 2.0 going to break my site and cause me hours of upgrade pain without someone to whom I can whimper, ‘Please, make it go again.’?”

    \n

    This post isn’t going to make that question much easier, but I may provide a toll-free support number for handling these issues before the week is out.

    \n

    There is a user-supplied list of plugins that will/will not work with WordPress 2.0 over on photomatt’s Codex user page. I hope that this page gets rolled into the main site soon because it’s a little off the beaten path.

    \n

    Here’s the good news: Themes are probably OK. Unless your theme was doing something crazy before, it’s probably going to work after the upgrade. There have been a lot of new plugin hooks added to the 2.0 version, but it doesn’t seem like anything made it into the theme system that would cause it to break.

    \n

    The theme system does allow for a custom plugin-like set of functions to be loaded. WordPress will automatically load a file named functions.php for any theme that includes one. This is done in the same way that plugins are loaded, and allows themes to supply their own custom features, including both output functions and configuration pages.

    \n

    Also, themes should now come with “thumbnail” images (I say “thumbnail” in quotes because they’re actually more the size of your palm, but…) that will be displayed in the admin. If your theme doesn’t have one, it’ll simply show a blank box.

    \n

    A theme that doesn’t have either one of these should load just as it did in WordPress 1.5.2, but won’t have those additional features.

    \n

    Plugins are another question, though. There are some specific sections of WordPress that have been heavily revised. If you use a plugin that integrates into one of these sections, it will probably not work. Specifically, the permissions and capability sections will be a bear for plugins that are affected by user levels.

    \n

    If you are running a “view level” plugin - something that restricts a reader’s permissions to access certain posts - I’ll say straight out right now that unless you’ve got a brand new version (and I know of no author who has written one of these plugins that is offering an updated version) it’s not going to work. If you have an update, comment here - I’m looking for it myself.

    \n

    One often overlooked aspect of this is that any plugin that uses an admin page will need review. The function call that adds custom admin pages used to require a user level number. It now requires a capability name. Now there are some backwards-compatibility checks for user levels in there, but they may obviously not work as well as something specifically designed to work with the new capabilities system.

    \n

    Also in the administration area, if you are using a plugin that hooks into the editor, you may have issues with it. This is particularly so if that plugin adds buttons to the Quicktag toolbar, since the new default is to use the WYSIWYG editor, which does not use the same toolbar.

    \n

    I have written the buttonsnap class, which lets plugin authors add buttons to both editors very easily. It’s not a plugin itself, but a library that makes it very simple for plugin authors to create buttons in either editor with a simple, single function call. Still, the plugin will have to be rewritten to use my library or some other solution if it’s going to be effectual.

    \n

    If your plugins use DOM to insert new elements into the user interface (pretty much any plugin that adds elements is using DOM to do it) then it may need to be updated to account for changes there.

    \n

    The Upload page in the admin is gone. If your plugin altered or attached to that page, it won’t be any more.

    \n

    Generally speaking, you’re going to need to procure write access for your server to the /wp-content directory of WordPress. That means you have to allow your server to write files into that directory from scripts. This will allow things like caching, image uploading, and the bundled backup plugin to work as they should, although your host may not like it or allow it (most hosts will, but you should check), and you might not consider it secure to leave all of the subdirectories there in that state.

    \n

    You should verify with someone you trust concerning the permissions you intend to use to provide the access to your server that you want to provide.

    \n

    What should you do if your plugin doesn’t work?

    \n

    Panic.

    \n

    Nah, just kidding. Actually, before you upgrade, compare the list of working plugins against the ones you’re using. If your doesn’t appear in the “known working” section of list, you need to talk to someone.

    \n

    Your best bet, if you can find contact info, is to badger the plugin author. Barring that, you can try any of the standard support channels, but be aware that in the fallout that is bound to occur when everyone upgrades, your 3rd-party code (code written by someone outside of the WordPress core) isn’t going to be a priority.

    \n

    I suggest trying WordPress IRC (#wordpress @ irc.freenode.net) and see if anyone there knows how to solve your problem. There is usually someone there that can at least record the issue or forward it on to someone who can start fixing.

    \n

    I haven’t been keeping up with documentation on 2.0 as well as I should, but I imagine that with the frequency of changes over the past month or two that nothing is verifiably up to date. If you find instructions in the Codex for performing some necessary intrinsic system change, first be sure that the instructions apply to the version you are installing.

    \n

    Probably the most sound advice I can give you is this: If you have any doubt about performing an upgrade - don’t upgrade! There’s no reason to submit yourself as a guinea pig to test this software, especially if you’re fairly satisfied with what you have already. Granted, WordPress 2.0 is a nice piece of work, but it’s going to be a while before 1.5.2 loses the support of the community at large.

    \n

    Take your time. Learn from the mistakes of the 500,000 other downloaders. After that, take the plunge.

    \n

    Remember to backup everything.

    \n

    Shameless plug: There are also people who can do your upgrade for you.

    \n

    If any of you developers think of any other “gotchas” in the new system, please leave them in the comments. Thanks!

    \n

    Otherwise, I’ll see you in WP 2.0 later this week. I’ve got at least two plugins for 2.0 that I hope to have baked for the release. I’m also looking forward to the sleek new WordPress.org site redesign/rebranding we were promised with the new version release.\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:8068:\"

    The most often asked question regarding WordPress 2.0 is, “When is it coming out?” Well, that’s going to be this week.

    \n

    The second most often asked question regarding WordPress 2.0 is, “What is changed?” I think I wrote about that before.

    \n

    The third most often asked question regarding WordPress 2.0 is, “Will my plugins and/or themes work?” Uh…. Possibly?
    \n
    \nThis is also known as the dreaded question, “In what way is WordPress 2.0 going to break my site and cause me hours of upgrade pain without someone to whom I can whimper, ‘Please, make it go again.’?”

    \n

    This post isn’t going to make that question much easier, but I may provide a toll-free support number for handling these issues before the week is out.

    \n

    There is a user-supplied list of plugins that will/will not work with WordPress 2.0 over on photomatt’s Codex user page. I hope that this page gets rolled into the main site soon because it’s a little off the beaten path.

    \n

    Here’s the good news: Themes are probably OK. Unless your theme was doing something crazy before, it’s probably going to work after the upgrade. There have been a lot of new plugin hooks added to the 2.0 version, but it doesn’t seem like anything made it into the theme system that would cause it to break.

    \n

    The theme system does allow for a custom plugin-like set of functions to be loaded. WordPress will automatically load a file named functions.php for any theme that includes one. This is done in the same way that plugins are loaded, and allows themes to supply their own custom features, including both output functions and configuration pages.

    \n

    Also, themes should now come with “thumbnail” images (I say “thumbnail” in quotes because they’re actually more the size of your palm, but…) that will be displayed in the admin. If your theme doesn’t have one, it’ll simply show a blank box.

    \n

    A theme that doesn’t have either one of these should load just as it did in WordPress 1.5.2, but won’t have those additional features.

    \n

    Plugins are another question, though. There are some specific sections of WordPress that have been heavily revised. If you use a plugin that integrates into one of these sections, it will probably not work. Specifically, the permissions and capability sections will be a bear for plugins that are affected by user levels.

    \n

    If you are running a “view level” plugin - something that restricts a reader’s permissions to access certain posts - I’ll say straight out right now that unless you’ve got a brand new version (and I know of no author who has written one of these plugins that is offering an updated version) it’s not going to work. If you have an update, comment here - I’m looking for it myself.

    \n

    One often overlooked aspect of this is that any plugin that uses an admin page will need review. The function call that adds custom admin pages used to require a user level number. It now requires a capability name. Now there are some backwards-compatibility checks for user levels in there, but they may obviously not work as well as something specifically designed to work with the new capabilities system.

    \n

    Also in the administration area, if you are using a plugin that hooks into the editor, you may have issues with it. This is particularly so if that plugin adds buttons to the Quicktag toolbar, since the new default is to use the WYSIWYG editor, which does not use the same toolbar.

    \n

    I have written the buttonsnap class, which lets plugin authors add buttons to both editors very easily. It’s not a plugin itself, but a library that makes it very simple for plugin authors to create buttons in either editor with a simple, single function call. Still, the plugin will have to be rewritten to use my library or some other solution if it’s going to be effectual.

    \n

    If your plugins use DOM to insert new elements into the user interface (pretty much any plugin that adds elements is using DOM to do it) then it may need to be updated to account for changes there.

    \n

    The Upload page in the admin is gone. If your plugin altered or attached to that page, it won’t be any more.

    \n

    Generally speaking, you’re going to need to procure write access for your server to the /wp-content directory of WordPress. That means you have to allow your server to write files into that directory from scripts. This will allow things like caching, image uploading, and the bundled backup plugin to work as they should, although your host may not like it or allow it (most hosts will, but you should check), and you might not consider it secure to leave all of the subdirectories there in that state.

    \n

    You should verify with someone you trust concerning the permissions you intend to use to provide the access to your server that you want to provide.

    \n

    What should you do if your plugin doesn’t work?

    \n

    Panic.

    \n

    Nah, just kidding. Actually, before you upgrade, compare the list of working plugins against the ones you’re using. If your doesn’t appear in the “known working” section of list, you need to talk to someone.

    \n

    Your best bet, if you can find contact info, is to badger the plugin author. Barring that, you can try any of the standard support channels, but be aware that in the fallout that is bound to occur when everyone upgrades, your 3rd-party code (code written by someone outside of the WordPress core) isn’t going to be a priority.

    \n

    I suggest trying WordPress IRC (#wordpress @ irc.freenode.net) and see if anyone there knows how to solve your problem. There is usually someone there that can at least record the issue or forward it on to someone who can start fixing.

    \n

    I haven’t been keeping up with documentation on 2.0 as well as I should, but I imagine that with the frequency of changes over the past month or two that nothing is verifiably up to date. If you find instructions in the Codex for performing some necessary intrinsic system change, first be sure that the instructions apply to the version you are installing.

    \n

    Probably the most sound advice I can give you is this: If you have any doubt about performing an upgrade - don’t upgrade! There’s no reason to submit yourself as a guinea pig to test this software, especially if you’re fairly satisfied with what you have already. Granted, WordPress 2.0 is a nice piece of work, but it’s going to be a while before 1.5.2 loses the support of the community at large.

    \n

    Take your time. Learn from the mistakes of the 500,000 other downloaders. After that, take the plunge.

    \n

    Remember to backup everything.

    \n

    Shameless plug: There are also people who can do your upgrade for you.

    \n

    If any of you developers think of any other “gotchas” in the new system, please leave them in the comments. Thanks!

    \n

    Otherwise, I’ll see you in WP 2.0 later this week. I’ve got at least two plugins for 2.0 that I hope to have baked for the release. I’m also looking forward to the sleek new WordPress.org site redesign/rebranding we were promised with the new version release.\n

    \";}i:27;a:6:{s:5:\"title\";s:69:\"Owen Winkler: WordPress and Philly Blogger Meetup Accosted by Santas!\";s:4:\"guid\";s:95:\"http://asymptomatic.net/2005/12/19/2169/wordpress-and-philly-blogger-meetup-accosted-by-santas/\";s:4:\"link\";s:95:\"http://asymptomatic.net/2005/12/19/2169/wordpress-and-philly-blogger-meetup-accosted-by-santas/\";s:11:\"description\";s:5698:\"

    Ah, the depressing dearth of WordPress meetup attendees this month was quite offset by the Philly Blogger meetup that happened afterward.

    \n

    First, let’s give props to the gang that showed up, and then I’ll tell you all about the crazy Irish dancing and Santa invasion that you missed when you left early.

    \n

    This list is mostly copied from the list at Philly Future, kindly collected and posted by Scott, and modified only slightly to point at posts talking about the meetup:

    \n\n

    And now, photos, movies, and “Run for your lives! It’s the Santas!”
    \n
    \nWell, I showed up way early for the meetup, which was quite shocking. Traffic was not bad on the expressway, since they must all have been gathering around the shopping malls.

    \n

    Lunch at Fregie’s is always tasty, which surprises me for Bar/Pub fair. The bartender even recognized me - not bad for less than a year of once-a-month meetups!

    \n

    Two guys showed for the WordPress portion of the evening - Mike, a local blogger, and masquerade, from WordPress IRC and all the way from Dover, both came and exchanged some WordPress discussion. Thanks to everyone who RSVP’ed, even if it was a “No”, since we know you’re still out there. Hopefully being after the holidays you’ll be able to hit our next meetup.

    \n

    After 3pm, we moved to the back table to sit with the Philly Blogger folks, and the group grew considerably. Perhaps next time I will take the opportunity to mingle more, to avoid being labeled as some kind of techie person. ;p

    \n

    As usual, the conversation was engaging. It’s nice to be around folks with considered thought that is out of the regular stream of things that I usually think about.

    \n

    As I said in my previous post, I’ve never seen Fergie’s so busy, but I guess bars in the city on the weekend tend to draw a crowd, especially on the holidays. The evening rolled on and a couple of folks started to leave for other destinations, but that’s when things started to get really weird.

    \n

    At 4pm they had started the Irish music. The fiddle players kept multiplying through the evening, and I think there were a total of 9 musicians by the end of the night. It was strange because every time I would look behind me, there would be another guy with a fiddle or a drum sitting at that table.

    \n

    The music was so good that it inspired three folks to spontaneously start Irish step dancing. (Cameraphone Video!) It was unexpected, yet very cool, and we all clapped along to the music and dancing. I’m not a judge as to how good they were, but they were better than I would have done, I assure you, and fun to watch.

    \n

    Not too long afterwards, the pub was overrun by Santas from the Santacon. Here is the Philly evite, and check out the photos from other cities, since the Philly Santas are obviously still too hung-over to have posted their own photos.

    \n

    Scott mentioned on Philly Future that there were 30 Santas. The capacity in this bar is 48. Yes, the place was actually freakin’ packed with Santas and their helpers, elves, and Miss Clauses alike.

    \n

    Need photographic evidence? Thank goodness for my Treo, or there might be only our word of this strange occurrance.

    \n

    Fortunately, the Santas left before we did, and we didn’t have to worry about the reindeer or sleighs causing any traffic issues when we were leaving.

    \n

    Ahem.

    \n

    I didn’t leave until around 9pm to pick Pat up from the airport. That’s a hella long meetup, and a roaring good time. Hopefully next month’s is as grand. Thanks everyone for coming out, see you next month!\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:5698:\"

    Ah, the depressing dearth of WordPress meetup attendees this month was quite offset by the Philly Blogger meetup that happened afterward.

    \n

    First, let’s give props to the gang that showed up, and then I’ll tell you all about the crazy Irish dancing and Santa invasion that you missed when you left early.

    \n

    This list is mostly copied from the list at Philly Future, kindly collected and posted by Scott, and modified only slightly to point at posts talking about the meetup:

    \n\n

    And now, photos, movies, and “Run for your lives! It’s the Santas!”
    \n
    \nWell, I showed up way early for the meetup, which was quite shocking. Traffic was not bad on the expressway, since they must all have been gathering around the shopping malls.

    \n

    Lunch at Fregie’s is always tasty, which surprises me for Bar/Pub fair. The bartender even recognized me - not bad for less than a year of once-a-month meetups!

    \n

    Two guys showed for the WordPress portion of the evening - Mike, a local blogger, and masquerade, from WordPress IRC and all the way from Dover, both came and exchanged some WordPress discussion. Thanks to everyone who RSVP’ed, even if it was a “No”, since we know you’re still out there. Hopefully being after the holidays you’ll be able to hit our next meetup.

    \n

    After 3pm, we moved to the back table to sit with the Philly Blogger folks, and the group grew considerably. Perhaps next time I will take the opportunity to mingle more, to avoid being labeled as some kind of techie person. ;p

    \n

    As usual, the conversation was engaging. It’s nice to be around folks with considered thought that is out of the regular stream of things that I usually think about.

    \n

    As I said in my previous post, I’ve never seen Fergie’s so busy, but I guess bars in the city on the weekend tend to draw a crowd, especially on the holidays. The evening rolled on and a couple of folks started to leave for other destinations, but that’s when things started to get really weird.

    \n

    At 4pm they had started the Irish music. The fiddle players kept multiplying through the evening, and I think there were a total of 9 musicians by the end of the night. It was strange because every time I would look behind me, there would be another guy with a fiddle or a drum sitting at that table.

    \n

    The music was so good that it inspired three folks to spontaneously start Irish step dancing. (Cameraphone Video!) It was unexpected, yet very cool, and we all clapped along to the music and dancing. I’m not a judge as to how good they were, but they were better than I would have done, I assure you, and fun to watch.

    \n

    Not too long afterwards, the pub was overrun by Santas from the Santacon. Here is the Philly evite, and check out the photos from other cities, since the Philly Santas are obviously still too hung-over to have posted their own photos.

    \n

    Scott mentioned on Philly Future that there were 30 Santas. The capacity in this bar is 48. Yes, the place was actually freakin’ packed with Santas and their helpers, elves, and Miss Clauses alike.

    \n

    Need photographic evidence? Thank goodness for my Treo, or there might be only our word of this strange occurrance.

    \n

    Fortunately, the Santas left before we did, and we didn’t have to worry about the reindeer or sleighs causing any traffic issues when we were leaving.

    \n

    Ahem.

    \n

    I didn’t leave until around 9pm to pick Pat up from the airport. That’s a hella long meetup, and a roaring good time. Hopefully next month’s is as grand. Thanks everyone for coming out, see you next month!\n

    \";}i:28;a:6:{s:5:\"title\";s:62:\"Owen Winkler: Philly WordPress Meetup - THIS SATURDAY, Dec. 17\";s:4:\"guid\";s:85:\"http://asymptomatic.net/2005/12/13/2155/philly-wordpress-meetup-this-saturday-dec-17/\";s:4:\"link\";s:85:\"http://asymptomatic.net/2005/12/13/2155/philly-wordpress-meetup-this-saturday-dec-17/\";s:11:\"description\";s:3081:\"

    “What’s this?” you ask. “This meetup is a week early!”

    \n

    That’s right! The dates have changed to avoid these blasted holidays. It seems like every week we have a meetup there is some holiday like Christmas or Memorial Day or something. So the monthly Philadelphia WordPress meetups are now the third Saturday of the month.

    \n

    Meetup time is 2pm. Immediately following the WordPress meetup at 3pm (at least, last I looked) is the Philadelphia Weblogger Meetup. It’s mostly Blogspot users, but they’re a fun group of prolific Philly bloggers. It’s nice to see how the others live.
    \n
    \nI want to see about moving the meetup to a cleaner venue. I like Fergie’s but it seems that some of the group isn’t down with the dark and smoky Irish bar scene. That’s fine, but I’ll need to find someplace new.

    \n

    Jim suggested World Cafe Live, and I’m going to drop by over there after the meetup to check out their space. I wonder how much that’s going to cost me. Hmm.

    \n

    In the future, assuming the group continues to show up, I would like to get some special guests. Maybe I could get them to Skype into the Meetup and answer some questions or tell us about what they’re working on. If you are interested in participating as a speaker, please let me know.

    \n

    If that pans out, I would like to produce a monthly podcast from the meetup. It would be a summary of topics covered, and everyone could get some mic time, including the special guest. I’m not sure how the format would work, but I think this would be of interest not only for luring more folks into our own den of geekines, but also for getting more people involved worldwide.

    \n

    Our group caters to a low-techie audience. I’ve answered a bunch of new user questions, and I’m happy to do it. Getting down with the people who actually use WordPress and make it popular gives me insight into how things can be made better for its audience. And sometimes I just like to help.

    \n

    Many of our unscripted topics are of interest to computer users in general. We don’t talk about WordPress and blogs through the whole meetup. So don’t let that scare you away. If you’re thinking of coming down for a walkthrough of the software, that’s not what this is really about. We can tell you what we like about it, how we’ve accomplished things with it, what we use it for, and what we write about. We can show you some new things, talk about new features, and give you a glimpse at the future. But really, we just like to get together and hang out.

    \n

    Please join us this weekend for our December meetup at 2pm. Meetups usually last for about 2 hours, sometimes longer. Fergie’s often has live Irish music at 4pm, which isn’t half bad. Come and enjoy!\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:3081:\"

    “What’s this?” you ask. “This meetup is a week early!”

    \n

    That’s right! The dates have changed to avoid these blasted holidays. It seems like every week we have a meetup there is some holiday like Christmas or Memorial Day or something. So the monthly Philadelphia WordPress meetups are now the third Saturday of the month.

    \n

    Meetup time is 2pm. Immediately following the WordPress meetup at 3pm (at least, last I looked) is the Philadelphia Weblogger Meetup. It’s mostly Blogspot users, but they’re a fun group of prolific Philly bloggers. It’s nice to see how the others live.
    \n
    \nI want to see about moving the meetup to a cleaner venue. I like Fergie’s but it seems that some of the group isn’t down with the dark and smoky Irish bar scene. That’s fine, but I’ll need to find someplace new.

    \n

    Jim suggested World Cafe Live, and I’m going to drop by over there after the meetup to check out their space. I wonder how much that’s going to cost me. Hmm.

    \n

    In the future, assuming the group continues to show up, I would like to get some special guests. Maybe I could get them to Skype into the Meetup and answer some questions or tell us about what they’re working on. If you are interested in participating as a speaker, please let me know.

    \n

    If that pans out, I would like to produce a monthly podcast from the meetup. It would be a summary of topics covered, and everyone could get some mic time, including the special guest. I’m not sure how the format would work, but I think this would be of interest not only for luring more folks into our own den of geekines, but also for getting more people involved worldwide.

    \n

    Our group caters to a low-techie audience. I’ve answered a bunch of new user questions, and I’m happy to do it. Getting down with the people who actually use WordPress and make it popular gives me insight into how things can be made better for its audience. And sometimes I just like to help.

    \n

    Many of our unscripted topics are of interest to computer users in general. We don’t talk about WordPress and blogs through the whole meetup. So don’t let that scare you away. If you’re thinking of coming down for a walkthrough of the software, that’s not what this is really about. We can tell you what we like about it, how we’ve accomplished things with it, what we use it for, and what we write about. We can show you some new things, talk about new features, and give you a glimpse at the future. But really, we just like to get together and hang out.

    \n

    Please join us this weekend for our December meetup at 2pm. Meetups usually last for about 2 hours, sometimes longer. Fergie’s often has live Irish music at 4pm, which isn’t half bad. Come and enjoy!\n

    \";}i:29;a:6:{s:5:\"title\";s:36:\"Owen Winkler: How to Patch WordPress\";s:4:\"guid\";s:63:\"http://asymptomatic.net/2005/12/03/2142/how-to-patch-wordpress/\";s:4:\"link\";s:63:\"http://asymptomatic.net/2005/12/03/2142/how-to-patch-wordpress/\";s:11:\"description\";s:5703:\"

    With the looming release of WordPress 2.0, there are a bunch of folks that are submitting bug fixes that say things like, “I would submit this to Trac, but I don’t know how.”

    \n

    I think even more people are suggesting that writing to the mailing list with their bugs to “confirm” them is better than submitting them directly to Trac. I suppose if you’re not sure something is a bug, it might be worthwhile to ask someone else (I would ask on IRC at #wordpress on irc.freenode.net) but it really is better to have a formal record of an issue, even if it turns out that what you’re experiencing is expected behavior. If you really fear that it’s something to do specifically with your installation, check on IRC or maybe try a fresh install.

    \n

    Nevertheless, it might be useful to folks to learn how I do it. I’m not saying my method is correct, just that it seems to get the job done with few complaints from the devs who commit my patches. It’s also a good method to use on Windows. (Sorry, someone else can document command-line Subversion use - not that I don’t know it, I’m just lazy.) Here we go…
    \n

    \n

    Here’s what you need to do to get an environment that is ripe for making source modifications:

    \n
      \n
    1. Get a WordPress.org Support Forum Login - You need a support forum login to gain access to Trac. Trac is the software WordPress uses for bug tracking, and it shares its login database with the support forum.
    2. \n
    3. Download and Install Tortoise SVN - Tortoise is the best Subversion client for Windows. It will let you get the latest source from the code repository without messing with funky nightly versions.
    4. \n
    5. Install XAMPP - If you’re not already running Apache locally, you’re going to need it. You can test under Microsoft’s IIS (and this is probably not a bad idea considering there are fewer people testing that environment) but if you’re not running Windows XP Pro (not Home), you may find this difficult. XAMPP is Apache, MySQL, PHP and some other thing you don’t need for WordPress all rolled into one bundle.
    6. \n
    7. Checkout a Copy of WordPress - Find the directory that XAMPP is using for its DocumentRoot and right-click on it (or create a “wordpress” directory there and right-click on that). Select SVN Checkout…. In the dialog, use http://svn.automattic.com/wordpress/trunk as the repository URL. Click OK to download the latest WordPress source.
    8. \n
    9. Make a Database - Use phpMyAdmin to create a new database for WordPress. Navigate to http://localhost/phpmyadmin to see the interface.
    10. \n
    11. Run the WordPress Install - I trust you already know how to do this. If not, then perhaps patching is a bit advanced, no?
    12. \n
    \n

    Ok, now WordPress is running. You should be able to do anything that you normally do with WordPress.

    \n

    At this point, you can modify code freely. If you make a change you don’t like, you can always switch back to the original code. Right-click on the file, choose Tortoise SVN > Revert and the file will be restored to the last checkout version.

    \n

    You should be able to see where you’ve made changes to the source by looking at the icon for the folder/file. The ones that are changed will likely be red.

    \n

    Here’s what you need to do to make a patch:

    \n
      \n
    1. Update your source - This step is pretty important, otherwise you might end up patching bugs back into source that’s already fixed. Subversion has integrated merge features. All you need to do is right-click on the wordpress root folder and select SVN Update. If any of the files you edited come up as conflicted (in red in the list), you will need to merge them by hand in the tool provided when you double-click on the list item.
    2. \n
    3. Create a patch - This is easy. Right-click on the wordpress root folder and select Tortoise SVN - Create Patch. A dialog appears to allow you to select the files you want to include in the patch. Check the files you want to include, then save the patch file. If I’m patching one file, I’ll usually use the full name of the file followed by “.diff”, for example, “admin-functions.php.diff”.
    4. \n
    5. Find a Trac Ticket - To submit a patch, you need a ticket. Be sure to search for an existing ticket for your issue before creating a new ticket. If one doesn’t exist, create one and explain the problem as clearly as possible. When you have a ticket, click Attach File and attach your patch file.
    6. \n
    \n

    That’s pretty much it, even though I’m sure I’ve left some trivial things out.

    \n

    The essential bit to recognize is that there really isn’t a good excuse for a developer to know enough to make code changes but not enough to use source control. If you don’t know source control, consider that learning this simple task is a great way to pad your resume. One way to scientifically demonstate that you can work well with teams is to show a proficiency with source code conrol tools. Hopefully this short description will help you on your way.

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:5703:\"

    With the looming release of WordPress 2.0, there are a bunch of folks that are submitting bug fixes that say things like, “I would submit this to Trac, but I don’t know how.”

    \n

    I think even more people are suggesting that writing to the mailing list with their bugs to “confirm” them is better than submitting them directly to Trac. I suppose if you’re not sure something is a bug, it might be worthwhile to ask someone else (I would ask on IRC at #wordpress on irc.freenode.net) but it really is better to have a formal record of an issue, even if it turns out that what you’re experiencing is expected behavior. If you really fear that it’s something to do specifically with your installation, check on IRC or maybe try a fresh install.

    \n

    Nevertheless, it might be useful to folks to learn how I do it. I’m not saying my method is correct, just that it seems to get the job done with few complaints from the devs who commit my patches. It’s also a good method to use on Windows. (Sorry, someone else can document command-line Subversion use - not that I don’t know it, I’m just lazy.) Here we go…
    \n

    \n

    Here’s what you need to do to get an environment that is ripe for making source modifications:

    \n
      \n
    1. Get a WordPress.org Support Forum Login - You need a support forum login to gain access to Trac. Trac is the software WordPress uses for bug tracking, and it shares its login database with the support forum.
    2. \n
    3. Download and Install Tortoise SVN - Tortoise is the best Subversion client for Windows. It will let you get the latest source from the code repository without messing with funky nightly versions.
    4. \n
    5. Install XAMPP - If you’re not already running Apache locally, you’re going to need it. You can test under Microsoft’s IIS (and this is probably not a bad idea considering there are fewer people testing that environment) but if you’re not running Windows XP Pro (not Home), you may find this difficult. XAMPP is Apache, MySQL, PHP and some other thing you don’t need for WordPress all rolled into one bundle.
    6. \n
    7. Checkout a Copy of WordPress - Find the directory that XAMPP is using for its DocumentRoot and right-click on it (or create a “wordpress” directory there and right-click on that). Select SVN Checkout…. In the dialog, use http://svn.automattic.com/wordpress/trunk as the repository URL. Click OK to download the latest WordPress source.
    8. \n
    9. Make a Database - Use phpMyAdmin to create a new database for WordPress. Navigate to http://localhost/phpmyadmin to see the interface.
    10. \n
    11. Run the WordPress Install - I trust you already know how to do this. If not, then perhaps patching is a bit advanced, no?
    12. \n
    \n

    Ok, now WordPress is running. You should be able to do anything that you normally do with WordPress.

    \n

    At this point, you can modify code freely. If you make a change you don’t like, you can always switch back to the original code. Right-click on the file, choose Tortoise SVN > Revert and the file will be restored to the last checkout version.

    \n

    You should be able to see where you’ve made changes to the source by looking at the icon for the folder/file. The ones that are changed will likely be red.

    \n

    Here’s what you need to do to make a patch:

    \n
      \n
    1. Update your source - This step is pretty important, otherwise you might end up patching bugs back into source that’s already fixed. Subversion has integrated merge features. All you need to do is right-click on the wordpress root folder and select SVN Update. If any of the files you edited come up as conflicted (in red in the list), you will need to merge them by hand in the tool provided when you double-click on the list item.
    2. \n
    3. Create a patch - This is easy. Right-click on the wordpress root folder and select Tortoise SVN - Create Patch. A dialog appears to allow you to select the files you want to include in the patch. Check the files you want to include, then save the patch file. If I’m patching one file, I’ll usually use the full name of the file followed by “.diff”, for example, “admin-functions.php.diff”.
    4. \n
    5. Find a Trac Ticket - To submit a patch, you need a ticket. Be sure to search for an existing ticket for your issue before creating a new ticket. If one doesn’t exist, create one and explain the problem as clearly as possible. When you have a ticket, click Attach File and attach your patch file.
    6. \n
    \n

    That’s pretty much it, even though I’m sure I’ve left some trivial things out.

    \n

    The essential bit to recognize is that there really isn’t a good excuse for a developer to know enough to make code changes but not enough to use source control. If you don’t know source control, consider that learning this simple task is a great way to pad your resume. One way to scientifically demonstate that you can work well with teams is to show a proficiency with source code conrol tools. Hopefully this short description will help you on your way.

    \";}i:30;a:6:{s:5:\"title\";s:44:\"Owen Winkler: What’s New in WordPress 2.0?\";s:4:\"guid\";s:66:\"http://asymptomatic.net/2005/11/29/2135/whats-new-in-wordpress-20/\";s:4:\"link\";s:66:\"http://asymptomatic.net/2005/11/29/2135/whats-new-in-wordpress-20/\";s:11:\"description\";s:7847:\"

    WordPress 2.0 isn’t out yet, but every day that I spend on the #wordpress IRC channel, I see this question go by at least once:

    \n

    What is new in WordPress 2.0 from 1.5?

    \n

    Wouldn’t you like to know?

    \n

    One important note before we begin: Many of the changes in WordPress from 1.5 to 2.0 are under the hood. They are things that you’re not going to notice unless you are developer. There are some features that casual users will notice that are significant, but (in my opinion) most of the real change has happened where most people won’t see.

    \n

    As a result, there is a fundamental thing to understand here. What often looks like catering to plugin developers is actually of benefit to common users, because with the enhanced capabilities of the underlying engine it becomes possible to make better extensions faster than we could before. The underlying engine has been made to work better. There have been times while doing contract work on 1.5.x installations where two days of work were necessary to accomplish something that I could have done in 1.6 (now 2.0) in about 10 minutes. Seriously.

    \n

    There has been a lot of talk in the WP scene about feature bloat, and it’s my own opinion that certain aspects of 2.0 are wildly overrated for what they do, but from an underlying technology standpoint, WordPress 2.0 is incredibly superior to the 1.5 codebase.

    \n

    So if you don’t want to upgrade because you don’t think that 2.0 offers you anything, just wait a couple months until the really fun plugins start appearing.

    \n

    Enough said on that. On with the new features list, which is by no means comprehensive:
    \n

    \n
      \n
    • More Abstracted Data Layer - The core WordPress code has been refactored to abstract direct calls to the database when adding posts, comments, and other data. This will lead to improvements in database access (perhaps even supporting other database engines in the future) and plugin development, including…
    • \n
    • New Import System - The new import system leverages the abstraction done at the data layer, so that import routines can call simple functions to convert posts from other blogging tools instead of a huge complicated series of queries. Importers are also available directly from the admin, so no special process need be taken to employ them. (Thanks, tinster.)
    • \n
    • Admin Redesign - It’s not so significant as the Tiger Admin plugin, but there are a few more gadgets in the admin, especially on the Write page. You can now drag sections of the page to reorganize them, and click the plus/minus to expand/contract the sections.
    • \n
    • The Rich Editor - WordPress has a new post editor built in that lets you see what you’re going to get without having to decipher tags. You can also resize the editing area on the fly by dragging it, which is pretty cool. Not everyone who has tried it likes the WYSIWYG editor, so there is an option to disable it on a per-user basis.
    • \n
    • Image/File Uploading - Just under the Rich Editor in the Write panel, there is a new control that allows you to upload and insert images into your posts. WordPress keeps track of these images and can even automatically provide dedicated pages to receive comments for them.
    • \n
    • Improved Post Preview - Instead of displaying the post as plain text below the editor, the post is now displayed in an embedded frame, using all of the layout and CSS that is normally applied to your site. In effect, the post looks exactly like it will when you publish it, giving you ample opportunity to review the post’s layout.
    • \n
    • User Metadata - To support user-based options, the user data now sports a much more flexible structure. People who use WordPress as a CMS can now use code to add custom data of any kind to any user profile.
    • \n
    • User Roles and Capabilities - The “user level” concept of security has now been replaced with Roles. WordPress associates a Role to each user. Roles have Capabilities such as “edit posts” and “activate plugins” that allow certain actions. There is no more concept of hierarchical users, but plugin authors can now create whole new Capabilities to apply proper permission management.
    • \n
    • Presentation Page Changes - WordPress 1.5 lets you switch themes, and 2.0 shows you what they look like before you do it. If a theme includes a screenshot, you’ll see it in the Presentation admin panel to help you choose the theme for your site.
    • \n
    • Ajax Category Addition - There’s a bunch of ajax in WordPress 2.0 and this is probably the most requested use of it. This feature lets you add new categories directly from the post-writing page.
    • \n
    • Ajax List Management - There are a few places in the admin that show lists of things and let you delete, like categories, posts, comments. Now, instead of reloading, the row turns red and then fades out.
    • \n
    • Moved Javascript/Images - Version 2.0 uses a lot more javascript than prior versions. Some utilities, like FAT (Fade Anything Technique) and SACK (Simple Ajax Code Kit), can be used by other tools and plugins, so it’s good to put them someplace where developers know they will reside, and outside of the admin directory, which might have weird permissions.
    • \n
    • Theme Admin Pages - The guys who worked on K2 went to some lengths to hack a custom configuration page for their theme into the WordPress admin. Now that capability is easily available by including a functions.php file with the theme. You can see this at work in the new header generator for the Default theme.
    • \n
    • Ping Delay Removed - Rather than pinging trackbacks and pingbacks when the post is saved (this causes the delay you see when posting to WP1.5), the pings are attempted via a different method that allows the admin interface to respond more quickly.
    • \n
    • Persistent Cache - There are certain types of queries that WordPress makes to the database repeatedly. To speed things up, the results of these queries are cached to disk. This caching is still compatible with other caching plugins, like WP-Cache, and could be just enough of a boost for larger sites to avoid optimizing everything.
    • \n
    • Database Versioning - Now when updates are made to the database schema, your admin panel will tell you to run the upgrade routines. This is handy because it keeps your database fresh enough to support the code that runs on it.
    • \n
    • New Built-In Plugins - WordPress 2.0 is now packed with the Akismet plugin for comment spam prevention, and the WP-DB-Backup plugin for manual or automated database backups.
    • \n
    \n

    Those are the major features added since 1.5.2 was released on August 20th. In addition to these major items (did I forget anything, anyone?) a few hundred bug fixes have been applied. I did not count them to know the specific number, but there were a lot. If you have a favorite bug that you wanted squashed, you should head over to the bug database and search for it. If you don’t find your bug, add it. (Use your WordPress support forum login for the bug database - It’s easy!)\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:7847:\"

    WordPress 2.0 isn’t out yet, but every day that I spend on the #wordpress IRC channel, I see this question go by at least once:

    \n

    What is new in WordPress 2.0 from 1.5?

    \n

    Wouldn’t you like to know?

    \n

    One important note before we begin: Many of the changes in WordPress from 1.5 to 2.0 are under the hood. They are things that you’re not going to notice unless you are developer. There are some features that casual users will notice that are significant, but (in my opinion) most of the real change has happened where most people won’t see.

    \n

    As a result, there is a fundamental thing to understand here. What often looks like catering to plugin developers is actually of benefit to common users, because with the enhanced capabilities of the underlying engine it becomes possible to make better extensions faster than we could before. The underlying engine has been made to work better. There have been times while doing contract work on 1.5.x installations where two days of work were necessary to accomplish something that I could have done in 1.6 (now 2.0) in about 10 minutes. Seriously.

    \n

    There has been a lot of talk in the WP scene about feature bloat, and it’s my own opinion that certain aspects of 2.0 are wildly overrated for what they do, but from an underlying technology standpoint, WordPress 2.0 is incredibly superior to the 1.5 codebase.

    \n

    So if you don’t want to upgrade because you don’t think that 2.0 offers you anything, just wait a couple months until the really fun plugins start appearing.

    \n

    Enough said on that. On with the new features list, which is by no means comprehensive:
    \n

    \n
      \n
    • More Abstracted Data Layer - The core WordPress code has been refactored to abstract direct calls to the database when adding posts, comments, and other data. This will lead to improvements in database access (perhaps even supporting other database engines in the future) and plugin development, including…
    • \n
    • New Import System - The new import system leverages the abstraction done at the data layer, so that import routines can call simple functions to convert posts from other blogging tools instead of a huge complicated series of queries. Importers are also available directly from the admin, so no special process need be taken to employ them. (Thanks, tinster.)
    • \n
    • Admin Redesign - It’s not so significant as the Tiger Admin plugin, but there are a few more gadgets in the admin, especially on the Write page. You can now drag sections of the page to reorganize them, and click the plus/minus to expand/contract the sections.
    • \n
    • The Rich Editor - WordPress has a new post editor built in that lets you see what you’re going to get without having to decipher tags. You can also resize the editing area on the fly by dragging it, which is pretty cool. Not everyone who has tried it likes the WYSIWYG editor, so there is an option to disable it on a per-user basis.
    • \n
    • Image/File Uploading - Just under the Rich Editor in the Write panel, there is a new control that allows you to upload and insert images into your posts. WordPress keeps track of these images and can even automatically provide dedicated pages to receive comments for them.
    • \n
    • Improved Post Preview - Instead of displaying the post as plain text below the editor, the post is now displayed in an embedded frame, using all of the layout and CSS that is normally applied to your site. In effect, the post looks exactly like it will when you publish it, giving you ample opportunity to review the post’s layout.
    • \n
    • User Metadata - To support user-based options, the user data now sports a much more flexible structure. People who use WordPress as a CMS can now use code to add custom data of any kind to any user profile.
    • \n
    • User Roles and Capabilities - The “user level” concept of security has now been replaced with Roles. WordPress associates a Role to each user. Roles have Capabilities such as “edit posts” and “activate plugins” that allow certain actions. There is no more concept of hierarchical users, but plugin authors can now create whole new Capabilities to apply proper permission management.
    • \n
    • Presentation Page Changes - WordPress 1.5 lets you switch themes, and 2.0 shows you what they look like before you do it. If a theme includes a screenshot, you’ll see it in the Presentation admin panel to help you choose the theme for your site.
    • \n
    • Ajax Category Addition - There’s a bunch of ajax in WordPress 2.0 and this is probably the most requested use of it. This feature lets you add new categories directly from the post-writing page.
    • \n
    • Ajax List Management - There are a few places in the admin that show lists of things and let you delete, like categories, posts, comments. Now, instead of reloading, the row turns red and then fades out.
    • \n
    • Moved Javascript/Images - Version 2.0 uses a lot more javascript than prior versions. Some utilities, like FAT (Fade Anything Technique) and SACK (Simple Ajax Code Kit), can be used by other tools and plugins, so it’s good to put them someplace where developers know they will reside, and outside of the admin directory, which might have weird permissions.
    • \n
    • Theme Admin Pages - The guys who worked on K2 went to some lengths to hack a custom configuration page for their theme into the WordPress admin. Now that capability is easily available by including a functions.php file with the theme. You can see this at work in the new header generator for the Default theme.
    • \n
    • Ping Delay Removed - Rather than pinging trackbacks and pingbacks when the post is saved (this causes the delay you see when posting to WP1.5), the pings are attempted via a different method that allows the admin interface to respond more quickly.
    • \n
    • Persistent Cache - There are certain types of queries that WordPress makes to the database repeatedly. To speed things up, the results of these queries are cached to disk. This caching is still compatible with other caching plugins, like WP-Cache, and could be just enough of a boost for larger sites to avoid optimizing everything.
    • \n
    • Database Versioning - Now when updates are made to the database schema, your admin panel will tell you to run the upgrade routines. This is handy because it keeps your database fresh enough to support the code that runs on it.
    • \n
    • New Built-In Plugins - WordPress 2.0 is now packed with the Akismet plugin for comment spam prevention, and the WP-DB-Backup plugin for manual or automated database backups.
    • \n
    \n

    Those are the major features added since 1.5.2 was released on August 20th. In addition to these major items (did I forget anything, anyone?) a few hundred bug fixes have been applied. I did not count them to know the specific number, but there were a lot. If you have a favorite bug that you wanted squashed, you should head over to the bug database and search for it. If you don’t find your bug, add it. (Use your WordPress support forum login for the bug database - It’s easy!)\n

    \";}i:31;a:6:{s:5:\"title\";s:30:\"Owen Winkler: Adhesive Updated\";s:4:\"guid\";s:57:\"http://asymptomatic.net/2005/11/21/2113/adhesive-updated/\";s:4:\"link\";s:57:\"http://asymptomatic.net/2005/11/21/2113/adhesive-updated/\";s:11:\"description\";s:1523:\"

    \"Adhesive I have updated Adhesive for WordPress 2.0 and released a preliminary archive of the plugin. There is a glitch in WordPress that prevents code in the footer of the post edit page from executing. I have submitted a support ticket with a patch for this issue. As soon as that patch is applied, things should work swimmingly.

    \n

    \"AdhesiveWhat’s new in Adhesive? Not much, really. The new version does make use of something new I’m trying out for configuration. Instead of using up a whole menu slot for a config page with only three options, I’ve made a kind of Ajax popup thing. You kind of need to see it to understand what I’m talking about.
    \n
    \nThis plugin is just the first plugin of a slew of updates that I’m planning for the next couple of weeks. I know that Adhesive is a popular plugin, so I hope that people who are trying out the WordPress 2.0 Beta will also give Adhesive a shot and let me know what issues they encounter.

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:1523:\"

    \"Adhesive I have updated Adhesive for WordPress 2.0 and released a preliminary archive of the plugin. There is a glitch in WordPress that prevents code in the footer of the post edit page from executing. I have submitted a support ticket with a patch for this issue. As soon as that patch is applied, things should work swimmingly.

    \n

    \"AdhesiveWhat’s new in Adhesive? Not much, really. The new version does make use of something new I’m trying out for configuration. Instead of using up a whole menu slot for a config page with only three options, I’ve made a kind of Ajax popup thing. You kind of need to see it to understand what I’m talking about.
    \n
    \nThis plugin is just the first plugin of a slew of updates that I’m planning for the next couple of weeks. I know that Adhesive is a popular plugin, so I hope that people who are trying out the WordPress 2.0 Beta will also give Adhesive a shot and let me know what issues they encounter.

    \";}i:32;a:6:{s:5:\"title\";s:39:\"Owen Winkler: Anybody Else Notice This?\";s:4:\"guid\";s:65:\"http://asymptomatic.net/2005/11/18/2107/anybody-else-notice-this/\";s:4:\"link\";s:65:\"http://asymptomatic.net/2005/11/18/2107/anybody-else-notice-this/\";s:11:\"description\";s:930:\"

    Beta1?

    \n

    As far as I know, the only other place you can find this out is via the support forums and the wp-testers mailing list. And since there’s a link to the downoad on the forum post, I’m assuming it’s a public beta.
    \n
    \nIf you’re brave, roll it out. If you’re not brave, you might want to wait for a second beta.

    \n

    If you’re looking for a list of changes since 1.5.x, you’re going to have to wait a little while for the compilation. There are a lot. Most of the stuff is internal, stuff that you’re not going to notice - and that stuff that goes unnoticed is why the system is great.\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:930:\"

    Beta1?

    \n

    As far as I know, the only other place you can find this out is via the support forums and the wp-testers mailing list. And since there’s a link to the downoad on the forum post, I’m assuming it’s a public beta.
    \n
    \nIf you’re brave, roll it out. If you’re not brave, you might want to wait for a second beta.

    \n

    If you’re looking for a list of changes since 1.5.x, you’re going to have to wait a little while for the compilation. There are a lot. Most of the stuff is internal, stuff that you’re not going to notice - and that stuff that goes unnoticed is why the system is great.\n

    \";}i:33;a:6:{s:5:\"title\";s:40:\"Owen Winkler: Skippy and Ryan in Hershey\";s:4:\"guid\";s:67:\"http://asymptomatic.net/2005/11/18/2106/skippy-and-ryan-in-hershey/\";s:4:\"link\";s:67:\"http://asymptomatic.net/2005/11/18/2106/skippy-and-ryan-in-hershey/\";s:11:\"description\";s:2773:\"

    \"SkippyI’ve been complacent in my posting this week. Too much to do between Riley’s birthday and work and not sleeping properly. I totally forgot to mention that I met with Skippy and Ryan this past Saturday.

    \n

    I met Skippy and Ryan both through WordPress, primarily by talking with them via IRC, and they’re both into WordPress and blogging enough that they beat me to the web to talk about our meeting.
    \n
    \n“Your Place” was a strangely named place to get together. I was very confused when Ryan first mentioned it. Also weird was that after my hour+ drive out there, the bardenter had me use the ladies’ room. It smelled of flowers and had no discolored puddles on the floor. Odd indeed.

    \n

    The company was quite cool. We discussed some WordPress things, of course, along with some other web technology stuff. We exchanged job descriptions, the typical first-meeting stuff. And there was beer, which is always good.

    \n

    I learned that Ryan’s getting married tomorrow, so stop by his site and wish him well. Yeah, I’ll drive an hour to meet with a guy and not even realize before I get there that he’s getting married in a week. I feel like a sap!

    \n

    Skippy game me a cool Skippy.net bumpersticker. I haven’t decided what to do with it yet. Maybe stick it to my computer case. Surely I won’t be applying Skippy’s mug to my bumper.

    \n

    Anyhow, I had a good retreat to chocolate country, and it was great to finally meet in person with these two guys I talk to all the time.

    \n

    This might be the last WordPress meetup for me this year, since the Philly meetup folks have the next meetup on Thanskgiving weekend, and I’ll be out of town. And December’s meetup is on Christmas eve, when I doubt I will find a blogger to hang with. So January’s meetup should offer my return to real-life appearances, if that means anything to you. I’m looking forward to catching up with masquerade from #wordpress IRC at that one. I’ll have to remind him to show.

    \n

    Someone needs to print #wordpress trading cards so we can collect them. I think Skippy’s ahead in the race so far.\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:2773:\"

    \"SkippyI’ve been complacent in my posting this week. Too much to do between Riley’s birthday and work and not sleeping properly. I totally forgot to mention that I met with Skippy and Ryan this past Saturday.

    \n

    I met Skippy and Ryan both through WordPress, primarily by talking with them via IRC, and they’re both into WordPress and blogging enough that they beat me to the web to talk about our meeting.
    \n
    \n“Your Place” was a strangely named place to get together. I was very confused when Ryan first mentioned it. Also weird was that after my hour+ drive out there, the bardenter had me use the ladies’ room. It smelled of flowers and had no discolored puddles on the floor. Odd indeed.

    \n

    The company was quite cool. We discussed some WordPress things, of course, along with some other web technology stuff. We exchanged job descriptions, the typical first-meeting stuff. And there was beer, which is always good.

    \n

    I learned that Ryan’s getting married tomorrow, so stop by his site and wish him well. Yeah, I’ll drive an hour to meet with a guy and not even realize before I get there that he’s getting married in a week. I feel like a sap!

    \n

    Skippy game me a cool Skippy.net bumpersticker. I haven’t decided what to do with it yet. Maybe stick it to my computer case. Surely I won’t be applying Skippy’s mug to my bumper.

    \n

    Anyhow, I had a good retreat to chocolate country, and it was great to finally meet in person with these two guys I talk to all the time.

    \n

    This might be the last WordPress meetup for me this year, since the Philly meetup folks have the next meetup on Thanskgiving weekend, and I’ll be out of town. And December’s meetup is on Christmas eve, when I doubt I will find a blogger to hang with. So January’s meetup should offer my return to real-life appearances, if that means anything to you. I’m looking forward to catching up with masquerade from #wordpress IRC at that one. I’ll have to remind him to show.

    \n

    Someone needs to print #wordpress trading cards so we can collect them. I think Skippy’s ahead in the race so far.\n

    \";}i:34;a:6:{s:5:\"title\";s:34:\"Owen Winkler: New Stuff at Red Alt\";s:4:\"guid\";s:61:\"http://asymptomatic.net/2005/11/10/2098/new-stuff-at-red-alt/\";s:4:\"link\";s:61:\"http://asymptomatic.net/2005/11/10/2098/new-stuff-at-red-alt/\";s:11:\"description\";s:3627:\"

    I don’t know why I didn’t think of this sooner.

    \n

    I downloaded a copy of bbPress to put on Red Alt to keep track of support issues. Hey, it’s not a ticket-based system, it’s better — It allows the community to pool their knowledge for support. Hopefully that means I won’t have to do it all myself.

    \n

    I’m making some other changes over at RedAlt, too. I’m slowly building a new download section so that things are easier to find. While I’m doing it, I’m writing up that missing documentation for the plugins that are laying around over there. Most of them didn’t really need documentation when they started, so I never wrote any, and then as they got more complicated they needed documentation that I never thought to write.

    \n

    Plus many of those plugins need WordPress 1.6 treatment. Some of them are already ready, just not sitting out there for download yet because they’re waiting for the organizational changes to come through. I’m thinking about how I’m going to organize everything so that you download the correct version for your copy of WordPress.
    \n
    \nI built a new forum for each plugin (although I think I might be missing one or two, now that I think about it). I’m quite pleased with the way I have been able to integrate the bbPress layout into the new site. How much tweaking I did to the bbPress templates - well, that’s another question. Some of the stuff I need to look at a little more closely because although I like the ajax features, I’m not fond of their layout. Specifically the topic informaton that appears near the top of each topic. I’m anxious to change the way it looks, but I don’t want to break the way ajax is working there.

    \n

    Something else I was considering is a plugin to synchronize the Red Alt forum user list with my WordPress user list here at Asymptomatic. If they were the same site, I think it would be easy. If they were on the same server it would be more possible. But neither of these are true. So it looks like there might be a need for a WordPress plugin that accepts requests from remote servers for user authentication. (Someone’s going to say “LDAP” and I’m going to say “Stick a sock in it”, but I’m just a curmudgeon.) I’ll need to think about alternatives to that.

    \n

    While I’m on the topic of plugins, someone needs to write a plugin that reorganizes the category list in the post-writing screen so that the most-used categories appear topmost in the list. This alphabetical business is for the birds. And while they’re at it, it might differentiate the most used category as posted from the bookmarklet, and use that as the default there. It’s a minor annoyance, since I only ever use the bookmarklet to post in one category (what you might call “asides”, the “linklog”) and I always have to scroll down to the “L”s and check that one when I just want to quick link to something. Maybe I should just change the bookmarklet functionality altogether.

    \n

    Now I’m kind of day-dreaming aloud. \":)\"

    \n

    Anyway, check out the new forum thing and let me know what you think. And, of course, post support issues over there where we can all get at them.\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:3627:\"

    I don’t know why I didn’t think of this sooner.

    \n

    I downloaded a copy of bbPress to put on Red Alt to keep track of support issues. Hey, it’s not a ticket-based system, it’s better — It allows the community to pool their knowledge for support. Hopefully that means I won’t have to do it all myself.

    \n

    I’m making some other changes over at RedAlt, too. I’m slowly building a new download section so that things are easier to find. While I’m doing it, I’m writing up that missing documentation for the plugins that are laying around over there. Most of them didn’t really need documentation when they started, so I never wrote any, and then as they got more complicated they needed documentation that I never thought to write.

    \n

    Plus many of those plugins need WordPress 1.6 treatment. Some of them are already ready, just not sitting out there for download yet because they’re waiting for the organizational changes to come through. I’m thinking about how I’m going to organize everything so that you download the correct version for your copy of WordPress.
    \n
    \nI built a new forum for each plugin (although I think I might be missing one or two, now that I think about it). I’m quite pleased with the way I have been able to integrate the bbPress layout into the new site. How much tweaking I did to the bbPress templates - well, that’s another question. Some of the stuff I need to look at a little more closely because although I like the ajax features, I’m not fond of their layout. Specifically the topic informaton that appears near the top of each topic. I’m anxious to change the way it looks, but I don’t want to break the way ajax is working there.

    \n

    Something else I was considering is a plugin to synchronize the Red Alt forum user list with my WordPress user list here at Asymptomatic. If they were the same site, I think it would be easy. If they were on the same server it would be more possible. But neither of these are true. So it looks like there might be a need for a WordPress plugin that accepts requests from remote servers for user authentication. (Someone’s going to say “LDAP” and I’m going to say “Stick a sock in it”, but I’m just a curmudgeon.) I’ll need to think about alternatives to that.

    \n

    While I’m on the topic of plugins, someone needs to write a plugin that reorganizes the category list in the post-writing screen so that the most-used categories appear topmost in the list. This alphabetical business is for the birds. And while they’re at it, it might differentiate the most used category as posted from the bookmarklet, and use that as the default there. It’s a minor annoyance, since I only ever use the bookmarklet to post in one category (what you might call “asides”, the “linklog”) and I always have to scroll down to the “L”s and check that one when I just want to quick link to something. Maybe I should just change the bookmarklet functionality altogether.

    \n

    Now I’m kind of day-dreaming aloud. \":)\"

    \n

    Anyway, check out the new forum thing and let me know what you think. And, of course, post support issues over there where we can all get at them.\n

    \";}i:35;a:6:{s:5:\"title\";s:48:\"Owen Winkler: How to use Flickr on WordPress.com\";s:4:\"guid\";s:74:\"http://asymptomatic.net/2005/11/03/2083/how-to-use-flickr-on-wordpresscom/\";s:4:\"link\";s:74:\"http://asymptomatic.net/2005/11/03/2083/how-to-use-flickr-on-wordpresscom/\";s:11:\"description\";s:1407:\"

    I wrote this post over at my WordPress.com blog, but I figured I would post it here because people actually read this one.

    \n

    You might have noticed the new tab in the media box at WordPress.com labeled “Browse Flickr”. I am partially responsible for that functionality. Andy Skelton coded the bulk of the media browser, and I added the Flickr tab by copying a lot of his code and mixing in the Flickr API.
    \n
    \nRather than bore you with written details of how it works, I’ll just direct you to the animations I made for using the media browser on both Internet Explorer and Firefox. Check them out, there are instructions in the photo descriptions for each!

    \n

    On Firefox: \"Flickr

    \n

    On Internet Explorer: \"Flickr\n

    \";s:7:\"pubdate\";s:31:\"Fri, 23 Dec 2005 06:00:15 +0000\";s:7:\"summary\";s:1407:\"

    I wrote this post over at my WordPress.com blog, but I figured I would post it here because people actually read this one.

    \n

    You might have noticed the new tab in the media box at WordPress.com labeled “Browse Flickr”. I am partially responsible for that functionality. Andy Skelton coded the bulk of the media browser, and I added the Flickr tab by copying a lot of his code and mixing in the Flickr API.
    \n
    \nRather than bore you with written details of how it works, I’ll just direct you to the animations I made for using the media browser on both Internet Explorer and Firefox. Check them out, there are instructions in the photo descriptions for each!

    \n

    On Firefox: \"Flickr

    \n

    On Internet Explorer: \"Flickr\n

    \";}i:36;a:6:{s:5:\"title\";s:56:\"Weblog Tools Collection: WordPress Upload Management Tip\";s:4:\"guid\";s:85:\"http://weblogtoolscollection.com/archives/2005/12/22/wordpress-upload-management-tip/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=538\";s:11:\"description\";s:214:\"

    WordPress Upload Management Tip: If you have a Wordpress upload folder (like my b2-img) that is overflowing with stuff.

    \";s:7:\"pubdate\";s:31:\"Thu, 22 Dec 2005 19:00:28 +0000\";s:7:\"summary\";s:214:\"

    WordPress Upload Management Tip: If you have a Wordpress upload folder (like my b2-img) that is overflowing with stuff.

    \";}i:37;a:6:{s:5:\"title\";s:31:\"Dougal Campbell: WordPress Book\";s:4:\"guid\";s:56:\"http://dougal.gunters.org/blog/2005/12/22/wordpress-book\";s:4:\"link\";s:56:\"http://dougal.gunters.org/blog/2005/12/22/wordpress-book\";s:11:\"description\";s:1931:\"
    \n\"Building\n

    \nBuilding Online Communities with Drupal, phpBB, and WordPress\n

    \n
    \n

    \nWell, actually, the book isn\'t just about WordPress. But it does devote six chapters to our favorite blogging platform. The book I\'m talking about is Building Online Communities with Drupal, phpBB, and WordPress, co-authored by Mike Little.\n

    \n
    \n

    \nThe result is six chapters on using WordPress to help build an online community. Although I wrote the book using version 1.5.x most of the WordPress chapters are version agnostic. This isn’t a “how to use WordPress†book (the excellent WordPress Codex is good for that). This is a book about how to use WordPress to help you build an online community.\n

    \n
    \n

    \nIt\'s published by Apress, who really seem to get the Open Source community. I\'ve got several other books by them, and I hope to write up some reviews eventually. I\'ll have to see if I can add Mike\'s book to my collection, too!\n

    \";s:7:\"pubdate\";s:31:\"Thu, 22 Dec 2005 16:49:12 +0000\";s:7:\"summary\";s:1931:\"
    \n\"Building\n

    \nBuilding Online Communities with Drupal, phpBB, and WordPress\n

    \n
    \n

    \nWell, actually, the book isn\'t just about WordPress. But it does devote six chapters to our favorite blogging platform. The book I\'m talking about is Building Online Communities with Drupal, phpBB, and WordPress, co-authored by Mike Little.\n

    \n
    \n

    \nThe result is six chapters on using WordPress to help build an online community. Although I wrote the book using version 1.5.x most of the WordPress chapters are version agnostic. This isn’t a “how to use WordPress†book (the excellent WordPress Codex is good for that). This is a book about how to use WordPress to help you build an online community.\n

    \n
    \n

    \nIt\'s published by Apress, who really seem to get the Open Source community. I\'ve got several other books by them, and I hope to write up some reviews eventually. I\'ll have to see if I can add Mike\'s book to my collection, too!\n

    \";}i:38;a:6:{s:5:\"title\";s:37:\"Mike Little: WordPress Book Published\";s:4:\"guid\";s:73:\"http://zed1.com/journalized/archives/2005/12/22/wordpress-book-published/\";s:4:\"link\";s:73:\"http://zed1.com/journalized/archives/2005/12/22/wordpress-book-published/\";s:11:\"description\";s:3535:\"

    \"TheWow! It’s hard to believe, but my first book is now a reality! Several copies of my first book — “Building Online Communities with Drupal, phpBB, and WordPress” arrived at my door this morning.

    \n

    This is the book, published by Apress that I have co-authored with Robert T Douglass and Jared Smith over the last six months or so. It has long been my ambition to be published and when the opportunity presented itself earlier this year, I had to grab it with both hands. It has been quite a hard struggle; writing in a small amount of spare time is not easy, but I do think it has been worth it.

    \n

    The result is six chapters on using WordPress to help build an online community. Although I wrote the book using version 1.5.x most of the WordPress chapters are version agnostic. This isn’t a “how to use WordPress” book (the excellent WordPress Codex is good for that). This is a book about how to use WordPress to help you build an online community.

    \n

    You can buy the book online directly from Apress including in eBook form. You can buy from Amazon.co.uk\"\" or you can buy it from Amazon.com\"\". I’m not sure whether it will be on the shelves of your local book store yet, but it will be over the next couple of days.

    \n

    Apress have a good summary of the book (my emphasis):

    \n

    \nContent management, blogs, and online forums are among the most significant online trends today, and Drupal, phpBB, and WordPress are three of the most popular open source applications facilitating these trends.

    \n

    Drupal is a full content management system that allows you to create any type of website you desire, from an e-commerce to a community-based site. phpBB enables you to set up a bulletin board or forum. And WordPress is the software of choice for the exploding blog community. All three technologies are based on PHP and MySQL.

    \n

    \"JamieFinally, I think Jamie is quite proud of her Dad, Jan is just glad it’s finally published! I must thank them both for putting up with me while I’ve struggled through this. The next one will be easier! I have to thank Matt, Ryan, and the rest of the WordPress community, without whom I would have had nothing to write about!\n

    \";s:7:\"pubdate\";s:31:\"Thu, 22 Dec 2005 12:30:18 +0000\";s:7:\"summary\";s:3535:\"

    \"TheWow! It’s hard to believe, but my first book is now a reality! Several copies of my first book — “Building Online Communities with Drupal, phpBB, and WordPress” arrived at my door this morning.

    \n

    This is the book, published by Apress that I have co-authored with Robert T Douglass and Jared Smith over the last six months or so. It has long been my ambition to be published and when the opportunity presented itself earlier this year, I had to grab it with both hands. It has been quite a hard struggle; writing in a small amount of spare time is not easy, but I do think it has been worth it.

    \n

    The result is six chapters on using WordPress to help build an online community. Although I wrote the book using version 1.5.x most of the WordPress chapters are version agnostic. This isn’t a “how to use WordPress” book (the excellent WordPress Codex is good for that). This is a book about how to use WordPress to help you build an online community.

    \n

    You can buy the book online directly from Apress including in eBook form. You can buy from Amazon.co.uk\"\" or you can buy it from Amazon.com\"\". I’m not sure whether it will be on the shelves of your local book store yet, but it will be over the next couple of days.

    \n

    Apress have a good summary of the book (my emphasis):

    \n

    \nContent management, blogs, and online forums are among the most significant online trends today, and Drupal, phpBB, and WordPress are three of the most popular open source applications facilitating these trends.

    \n

    Drupal is a full content management system that allows you to create any type of website you desire, from an e-commerce to a community-based site. phpBB enables you to set up a bulletin board or forum. And WordPress is the software of choice for the exploding blog community. All three technologies are based on PHP and MySQL.

    \n

    \"JamieFinally, I think Jamie is quite proud of her Dad, Jan is just glad it’s finally published! I must thank them both for putting up with me while I’ve struggled through this. The next one will be easier! I have to thank Matt, Ryan, and the rest of the WordPress community, without whom I would have had nothing to write about!\n

    \";}i:39;a:6:{s:5:\"title\";s:65:\"Weblog Tools Collection: Yahoo YPN, Chitika, Adsense Preview Tool\";s:4:\"guid\";s:92:\"http://weblogtoolscollection.com/archives/2005/12/22/yahoo-ypn-chitika-adsense-preview-tool/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=537\";s:11:\"description\";s:505:\"

    Yahoo YPN, Chitika, Adsense Preview Tool: Preview all your ads in one location with Firefox and/or IE.

    \nTechnorati Tags: adsense preview chitika preview ypn preview\";s:7:\"pubdate\";s:31:\"Thu, 22 Dec 2005 11:42:29 +0000\";s:7:\"summary\";s:505:\"

    Yahoo YPN, Chitika, Adsense Preview Tool: Preview all your ads in one location with Firefox and/or IE.

    \nTechnorati Tags: adsense preview chitika preview ypn preview\";}i:40;a:6:{s:5:\"title\";s:53:\"Dougal Campbell: WordPress 2.0 Release on December 26\";s:4:\"guid\";s:77:\"http://dougal.gunters.org/blog/2005/12/21/wordpress-20-release-on-december-26\";s:4:\"link\";s:77:\"http://dougal.gunters.org/blog/2005/12/21/wordpress-20-release-on-december-26\";s:11:\"description\";s:324:\"

    \nIn response to concerns about availability of support over the Christmas holiday, the release of WordPress 2.0 has been delayed until December 26. This will allow all the fine helpers who answer questions in the forums and on IRC to enjoy the holiday without feeling that they need to rush back to assist upgraders.\n

    \";s:7:\"pubdate\";s:31:\"Thu, 22 Dec 2005 04:09:39 +0000\";s:7:\"summary\";s:324:\"

    \nIn response to concerns about availability of support over the Christmas holiday, the release of WordPress 2.0 has been delayed until December 26. This will allow all the fine helpers who answer questions in the forums and on IRC to enjoy the holiday without feeling that they need to rush back to assist upgraders.\n

    \";}i:41;a:6:{s:5:\"title\";s:50:\"Dougal Campbell: Yahoo! Web Hosting adds WordPress\";s:4:\"guid\";s:32:\"http://dougal.gunters.org/?p=691\";s:4:\"link\";s:74:\"http://dougal.gunters.org/blog/2005/12/20/yahoo-web-hosting-adds-wordpress\";s:11:\"description\";s:432:\"

    \nDouble Yow! Another announcement from Matt: Yahoo! Web Hosting has added WordPress to their offerings. And as an added bonus, they automatically enable the Akismet plugin, which is going to add a wealth of data to that service\'s spam filters.\n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 19:00:05 +0000\";s:7:\"summary\";s:432:\"

    \nDouble Yow! Another announcement from Matt: Yahoo! Web Hosting has added WordPress to their offerings. And as an added bonus, they automatically enable the Akismet plugin, which is going to add a wealth of data to that service\'s spam filters.\n

    \";}i:42;a:6:{s:5:\"title\";s:47:\"Dougal Campbell: WordPress 2.0 Release imminent\";s:4:\"guid\";s:32:\"http://dougal.gunters.org/?p=690\";s:4:\"link\";s:71:\"http://dougal.gunters.org/blog/2005/12/19/wordpress-20-release-imminent\";s:11:\"description\";s:2468:\"

    \nJust three hours ago, Matt posted this on the wordpress-hackers mailing list:\n

    \n
    \n

    Subject: [wp-hackers] 2.0 Release

    \n

    \nWednesday or Thursday, depending on the phase of the moon.\n

    \n

    \nNow would be a great time to start making sure your themes and plugins work with the new version, and post to hackers if you need any help updating them. \n

    \n
    \n

    \nYow! On the one hand, I\'m really excited about this release. A lot of the changes are under the hood, with tons of new API hooks, a new object cache system, new user \"roles and capabilities\" security model, fixes and code refactoring. The major immediate change that most users will see is the new WYSIWYG post editor. Personally, though, I\'ll turn that off, because I prefer crafting the xhtml for my posts by hand. \n

    \n

    \nThe new API hooks are going to enable a whole new class of themes for WordPress which have built-in customization options. An early example is Michael Heilemann\'s K2 (which is actually built off of WordPress 1.5, but will take advantage of 2.0 features soon).\n

    \n

    \nOn the other hand, as you might infer from the \'2.0\' version number, this is a major change release. You may very well run into \'issues\' (as we in the software development community like to politely call them) when upgrading from older versions of WordPress. I strongly suggest that anyone who is upgrading an older version of WordPress to 2.0 should backup everything. Backup your entire existing wordpress directory and your database tables. This is particularly true if you rely on third-party themes or plugins. Most people will probably upgrade without incident. For for those (hopefully) few who run into problems, a backup is going to be essential.\n

    \n

    \nThat said, it looks like it could be a very Merry Christmas indeed for us WordPress users \":)\" \n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 19:00:05 +0000\";s:7:\"summary\";s:2468:\"

    \nJust three hours ago, Matt posted this on the wordpress-hackers mailing list:\n

    \n
    \n

    Subject: [wp-hackers] 2.0 Release

    \n

    \nWednesday or Thursday, depending on the phase of the moon.\n

    \n

    \nNow would be a great time to start making sure your themes and plugins work with the new version, and post to hackers if you need any help updating them. \n

    \n
    \n

    \nYow! On the one hand, I\'m really excited about this release. A lot of the changes are under the hood, with tons of new API hooks, a new object cache system, new user \"roles and capabilities\" security model, fixes and code refactoring. The major immediate change that most users will see is the new WYSIWYG post editor. Personally, though, I\'ll turn that off, because I prefer crafting the xhtml for my posts by hand. \n

    \n

    \nThe new API hooks are going to enable a whole new class of themes for WordPress which have built-in customization options. An early example is Michael Heilemann\'s K2 (which is actually built off of WordPress 1.5, but will take advantage of 2.0 features soon).\n

    \n

    \nOn the other hand, as you might infer from the \'2.0\' version number, this is a major change release. You may very well run into \'issues\' (as we in the software development community like to politely call them) when upgrading from older versions of WordPress. I strongly suggest that anyone who is upgrading an older version of WordPress to 2.0 should backup everything. Backup your entire existing wordpress directory and your database tables. This is particularly true if you rely on third-party themes or plugins. Most people will probably upgrade without incident. For for those (hopefully) few who run into problems, a backup is going to be essential.\n

    \n

    \nThat said, it looks like it could be a very Merry Christmas indeed for us WordPress users \":)\" \n

    \";}i:43;a:6:{s:5:\"title\";s:35:\"Dougal Campbell: Poisoning the well\";s:4:\"guid\";s:32:\"http://dougal.gunters.org/?p=689\";s:4:\"link\";s:60:\"http://dougal.gunters.org/blog/2005/12/06/poisoning-the-well\";s:11:\"description\";s:3075:\"

    \nOverall, the volume of spam attempts on my server have been down lately. Oh, I still get a steady stream, I delete over 100 comment spams (caught by my filters) each day. But I\'ve seen fewer of the massive, server-squashing spam runs that hammer my web service with too many simultaneous connections, blocking out legitimate users.\n

    \n

    \nOn the other hand, I\'m seeing a lot more attempts by spammers to poison the well. What I mean by that is that they are submitting bogus comments, full of non-spammy (but more-or-less random) content, and links to legitimate web sites. For example:\n

    \n

    Name: Adam Baumann

    \n\n

    Hi. Just letting you know that I enjoyed your site. when Soldier Double Game Lose: http://www.newscorp.com/ , to Bet Opponents you should be very Faithful Big Gnome becomes Industrious Plane in final , Superb Opponents becomes Superb Soldier in final Faithful is feature of White Circle\n

    \n
    \n

    \nThe comment is obviously gibberish, right? And the links are all to perfectly normal -- in fact, popular -- sites. You might wonder why a spammer would bother posting it. The idea is to poison the well of any sites which use Bayesian techniques to classify content as spam or not. By tricking sites into classifying \"good\" content as \"spam\", they (theoretically) can reduce the effectiveness of the spam filters.\n

    \n

    \nWith enough poisoning, your spam filter may start getting false-positives, which are legitimate messages that have incorrectly been tagged as spam. And if you get enough false-positives, you\'ll lose faith in your spam filter and disable it. At least, that\'s what the spammers are trying to accomplish.\n

    \n

    \nWill their plan work? I guess that depends on your particular spam filters. I\'m betting that systems like Akismet, which collect data from a wide variety of sources, will probably be able to defend against Bayes poisoning. How? Well, there\'s this thing called an IP address. Even though the spammers submit their garbage via an army of anonymous proxy servers and zombie machines, they still only have access to a finite number of hosts, a limited number of IP addresses. It won\'t take long for those IPs to be statistically classified as sources of spam. An IP like 221.3.235.96 will be flagged as a spam indicator far sooner than the words \"Industrious\" and \"Soldier\".\n

    \n

    \nSo once again I say, thank you, spammers. We\'re learning more about you every day.\n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 19:00:05 +0000\";s:7:\"summary\";s:3075:\"

    \nOverall, the volume of spam attempts on my server have been down lately. Oh, I still get a steady stream, I delete over 100 comment spams (caught by my filters) each day. But I\'ve seen fewer of the massive, server-squashing spam runs that hammer my web service with too many simultaneous connections, blocking out legitimate users.\n

    \n

    \nOn the other hand, I\'m seeing a lot more attempts by spammers to poison the well. What I mean by that is that they are submitting bogus comments, full of non-spammy (but more-or-less random) content, and links to legitimate web sites. For example:\n

    \n

    Name: Adam Baumann

    \n\n

    Hi. Just letting you know that I enjoyed your site. when Soldier Double Game Lose: http://www.newscorp.com/ , to Bet Opponents you should be very Faithful Big Gnome becomes Industrious Plane in final , Superb Opponents becomes Superb Soldier in final Faithful is feature of White Circle\n

    \n
    \n

    \nThe comment is obviously gibberish, right? And the links are all to perfectly normal -- in fact, popular -- sites. You might wonder why a spammer would bother posting it. The idea is to poison the well of any sites which use Bayesian techniques to classify content as spam or not. By tricking sites into classifying \"good\" content as \"spam\", they (theoretically) can reduce the effectiveness of the spam filters.\n

    \n

    \nWith enough poisoning, your spam filter may start getting false-positives, which are legitimate messages that have incorrectly been tagged as spam. And if you get enough false-positives, you\'ll lose faith in your spam filter and disable it. At least, that\'s what the spammers are trying to accomplish.\n

    \n

    \nWill their plan work? I guess that depends on your particular spam filters. I\'m betting that systems like Akismet, which collect data from a wide variety of sources, will probably be able to defend against Bayes poisoning. How? Well, there\'s this thing called an IP address. Even though the spammers submit their garbage via an army of anonymous proxy servers and zombie machines, they still only have access to a finite number of hosts, a limited number of IP addresses. It won\'t take long for those IPs to be statistically classified as sources of spam. An IP like 221.3.235.96 will be flagged as a spam indicator far sooner than the words \"Industrious\" and \"Soldier\".\n

    \n

    \nSo once again I say, thank you, spammers. We\'re learning more about you every day.\n

    \";}i:44;a:6:{s:5:\"title\";s:40:\"Dougal Campbell: The State of FeedLounge\";s:4:\"guid\";s:32:\"http://dougal.gunters.org/?p=688\";s:4:\"link\";s:65:\"http://dougal.gunters.org/blog/2005/12/05/the-state-of-feedlounge\";s:11:\"description\";s:1368:\"

    \nAlex has posted a moderately detailed article explaining The State of FeedLounge. For any newcomers who don\'t know what FeedLounge is, it\'s a web based feed aggregator, not unlike BlogLines. But FeedLounge is a next-generation web service that looks and acts more like a desktop application than a web site.\n

    \n

    \nI\'m one of the lucky alpha testers who have had access for the last six months. I committed to using FeedLounge as my only feed reader, dumping SharpReader and the RSS capabilities of Thunderbird. I\'ve watched FeedLounge go through some rough spots, and it always came back better than before. Alex and Scott have done an outstanding job with everything from the great AJAXian user interface to the invisible backend. When the time comes, if the price is right, I\'m going to be a paying customer.\n

    \n

    \nBut you don\'t care about all that, do you? What good is it to read about a service that you don\'t have access to? All you care about is knowing that FeedLounge will be open for public beta on January 16, 2006. \n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 19:00:05 +0000\";s:7:\"summary\";s:1368:\"

    \nAlex has posted a moderately detailed article explaining The State of FeedLounge. For any newcomers who don\'t know what FeedLounge is, it\'s a web based feed aggregator, not unlike BlogLines. But FeedLounge is a next-generation web service that looks and acts more like a desktop application than a web site.\n

    \n

    \nI\'m one of the lucky alpha testers who have had access for the last six months. I committed to using FeedLounge as my only feed reader, dumping SharpReader and the RSS capabilities of Thunderbird. I\'ve watched FeedLounge go through some rough spots, and it always came back better than before. Alex and Scott have done an outstanding job with everything from the great AJAXian user interface to the invisible backend. When the time comes, if the price is right, I\'m going to be a paying customer.\n

    \n

    \nBut you don\'t care about all that, do you? What good is it to read about a service that you don\'t have access to? All you care about is knowing that FeedLounge will be open for public beta on January 16, 2006. \n

    \";}i:45;a:6:{s:5:\"title\";s:37:\"Dougal Campbell: Breaking the silence\";s:4:\"guid\";s:32:\"http://dougal.gunters.org/?p=685\";s:4:\"link\";s:62:\"http://dougal.gunters.org/blog/2005/11/23/breaking-the-silence\";s:11:\"description\";s:1735:\"

    \nBetween projects at work (rolling out the largest indoor WiFi network in the world at a certain international airport near Atlanta) and projects at home (building more IKEA furniture -- bed, dresser, chest, entertainment center, end tables, ottomans; doing what I can to help Suze prepare for Thanksgiving), I haven\'t had much time for posting lately. But I\'m making a minute to come up for a quick breath and note some of the things that have caught my attention lately:\n

    \n\n

    \nAnd of course, there are the big stories that I won\'t bother linking to, because you can hardly visit a tech/link site without bumping into them: Google Base, Sony Rootkit DRM, Tivo video on iPod and PSP, etc.\n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 19:00:05 +0000\";s:7:\"summary\";s:1735:\"

    \nBetween projects at work (rolling out the largest indoor WiFi network in the world at a certain international airport near Atlanta) and projects at home (building more IKEA furniture -- bed, dresser, chest, entertainment center, end tables, ottomans; doing what I can to help Suze prepare for Thanksgiving), I haven\'t had much time for posting lately. But I\'m making a minute to come up for a quick breath and note some of the things that have caught my attention lately:\n

    \n\n

    \nAnd of course, there are the big stories that I won\'t bother linking to, because you can hardly visit a tech/link site without bumping into them: Google Base, Sony Rootkit DRM, Tivo video on iPod and PSP, etc.\n

    \";}i:46;a:6:{s:5:\"title\";s:40:\"Dougal Campbell: Blog Software Smackdown\";s:4:\"guid\";s:32:\"http://dougal.gunters.org/?p=684\";s:4:\"link\";s:65:\"http://dougal.gunters.org/blog/2005/11/15/blog-software-smackdown\";s:11:\"description\";s:1027:\"

    \nThanks to my friend Mike, who pointed out the Blog Software Smackdown article over on SitePoint. Author Vinnie Garcia compares the \"big three\" of blogging software: Movable Type, WordPress, and Textpattern. He gives a good overview of each, discusses their strengths and possible pitfalls, and provides links to example sites for each.\n

    \n

    \nIn the final analysis, he compares the systems in 9 areas, grading them on a five point scale. If you average the scores, the final tally is:\n

    \n
    \n
    Movable Type
    3.72
    \n
    Textpattern
    3.88
    \n
    WordPress
    4.38
    \n
    \n

    \nOf course, that\'s pretty subjective. What really matters is the feature set that\'s important to you, and which tool addresses them in the best way. But it\'s good to see that WP shines in this comparison.\n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 19:00:05 +0000\";s:7:\"summary\";s:1027:\"

    \nThanks to my friend Mike, who pointed out the Blog Software Smackdown article over on SitePoint. Author Vinnie Garcia compares the \"big three\" of blogging software: Movable Type, WordPress, and Textpattern. He gives a good overview of each, discusses their strengths and possible pitfalls, and provides links to example sites for each.\n

    \n

    \nIn the final analysis, he compares the systems in 9 areas, grading them on a five point scale. If you average the scores, the final tally is:\n

    \n
    \n
    Movable Type
    3.72
    \n
    Textpattern
    3.88
    \n
    WordPress
    4.38
    \n
    \n

    \nOf course, that\'s pretty subjective. What really matters is the feature set that\'s important to you, and which tool addresses them in the best way. But it\'s good to see that WP shines in this comparison.\n

    \";}i:47;a:6:{s:5:\"title\";s:25:\"Dougal Campbell: Upgraded\";s:4:\"guid\";s:52:\"http://dougal.gunters.org/blog/2005/12/21/upgraded-2\";s:4:\"link\";s:52:\"http://dougal.gunters.org/blog/2005/12/21/upgraded-2\";s:11:\"description\";s:899:\"

    \nI went ahead and took the plunge: this site is now running WordPress 2.0 RC3. Everything seems to be working fine so far, despite some of the ugly hacks I have in my home-grown \"Rock\'em Sock\'em Robots\" theme. I\'m not using the new WYSIWYG editor, as I prefer to write my post markup by hand. And even though the new version lets you add categories on-the-fly, I\'m still using the Tags4WP plugin, because it will auto-complete category names for me as I type. And likewise, I\'ll probably continue to use the Image Browser plugin, because I don\'t like how the built-in media manager stores images in subfolders by date. What I probably need to do is create a plugin to customize the upload location (using the upload_dir filter).\n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 18:37:07 +0000\";s:7:\"summary\";s:899:\"

    \nI went ahead and took the plunge: this site is now running WordPress 2.0 RC3. Everything seems to be working fine so far, despite some of the ugly hacks I have in my home-grown \"Rock\'em Sock\'em Robots\" theme. I\'m not using the new WYSIWYG editor, as I prefer to write my post markup by hand. And even though the new version lets you add categories on-the-fly, I\'m still using the Tags4WP plugin, because it will auto-complete category names for me as I type. And likewise, I\'ll probably continue to use the Image Browser plugin, because I don\'t like how the built-in media manager stores images in subfolders by date. What I probably need to do is create a plugin to customize the upload location (using the upload_dir filter).\n

    \";}i:48;a:6:{s:5:\"title\";s:54:\"Weblog Tools Collection: Mullenweg launches Automattic\";s:4:\"guid\";s:83:\"http://weblogtoolscollection.com/archives/2005/12/21/mullenweg-launches-automattic/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=536\";s:11:\"description\";s:664:\"

    Mullenweg launches Automattic: There are times in ones’ life when they kick themselves for not having done something that was staring them in the face, feel like they have let themselves down but are still incerdibly happy for the way the cookie crumbled. This happens to be one such situation for me. Congratulations Matt! This is well deserved and I laud you (and your cohorts) and your efforts.

    \nTechnorati Tags: automattic\";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 17:00:20 +0000\";s:7:\"summary\";s:664:\"

    Mullenweg launches Automattic: There are times in ones’ life when they kick themselves for not having done something that was staring them in the face, feel like they have let themselves down but are still incerdibly happy for the way the cookie crumbled. This happens to be one such situation for me. Congratulations Matt! This is well deserved and I laud you (and your cohorts) and your efforts.

    \nTechnorati Tags: automattic\";}i:49;a:6:{s:5:\"title\";s:57:\"Weblog Tools Collection: Moving from TypePad to WordPress\";s:4:\"guid\";s:86:\"http://weblogtoolscollection.com/archives/2005/12/21/moving-from-typepad-to-wordpress/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=534\";s:11:\"description\";s:301:\"

    Moving from TypePad to WordPress: Detailed steps on how to manually make the move without losing anythins of consequence. Especially relevant if you are moving to the new Yahoo! Hosting service (congrats Matt!)\n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 12:11:07 +0000\";s:7:\"summary\";s:301:\"

    Moving from TypePad to WordPress: Detailed steps on how to manually make the move without losing anythins of consequence. Especially relevant if you are moving to the new Yahoo! Hosting service (congrats Matt!)\n

    \";}i:50;a:6:{s:5:\"title\";s:30:\"Donncha: WordPress.com Goodies\";s:4:\"guid\";s:59:\"http://blogs.linux.ie/xeer/2005/12/21/wordpresscom-goodies/\";s:4:\"link\";s:59:\"http://blogs.linux.ie/xeer/2005/12/21/wordpresscom-goodies/\";s:11:\"description\";s:1512:\"

    Yesterday it was announced that Yahoo will bundle WordPress right alongside Movable Type on their website! Matt and the rest of us were keeping mum on everything until yesterday, but now it’s out I can blog this. Matt has links to online coverage of the event.
    \nMe? I think it’s great, the best blogging platform just got a huge boost from a very major player on the Internet! Maybe when I talk to people about blogs in the future they’ll know what I’m talking about!

    \n

    The new Automattic website launched yesterday too. There’s a little blurb about me on the About page, although I do not drink Murphys except for that time years ago when I drank a pint with a shot of Baileys to help it go down.. that was interesting.

    \n

    In other news, Performancing for Firefox came out. It’s a blog editor extension for Firefox that looks pretty cool. Unfortunately I’m not using Firefox 1.5 yet so I can’t try it out.\n

    \";s:7:\"pubdate\";s:31:\"Wed, 21 Dec 2005 08:16:22 +0000\";s:7:\"summary\";s:1512:\"

    Yesterday it was announced that Yahoo will bundle WordPress right alongside Movable Type on their website! Matt and the rest of us were keeping mum on everything until yesterday, but now it’s out I can blog this. Matt has links to online coverage of the event.
    \nMe? I think it’s great, the best blogging platform just got a huge boost from a very major player on the Internet! Maybe when I talk to people about blogs in the future they’ll know what I’m talking about!

    \n

    The new Automattic website launched yesterday too. There’s a little blurb about me on the About page, although I do not drink Murphys except for that time years ago when I drank a pint with a shot of Baileys to help it go down.. that was interesting.

    \n

    In other news, Performancing for Firefox came out. It’s a blog editor extension for Firefox that looks pretty cool. Unfortunately I’m not using Firefox 1.5 yet so I can’t try it out.\n

    \";}i:51;a:6:{s:5:\"title\";s:26:\"Matt: On Automattic Launch\";s:4:\"guid\";s:53:\"http://photomatt.net/2005/12/20/on-automattic-launch/\";s:4:\"link\";s:53:\"http://photomatt.net/2005/12/20/on-automattic-launch/\";s:11:\"description\";s:657:\"

    SiliconBeat has a good article on the launch of Automattic and the Yahoo deal. We also ended up in Infoworld. Scott Gatz wrote a bit about the Yahoo deal, saying “The Hosting team also did a similar deal with Moveable Type last week, so if you prefer that, its just as easy. (although I personally prefer the simplicity and extensibility of WordPress).”\n

    \";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 22:04:13 +0000\";s:7:\"summary\";s:657:\"

    SiliconBeat has a good article on the launch of Automattic and the Yahoo deal. We also ended up in Infoworld. Scott Gatz wrote a bit about the Yahoo deal, saying “The Hosting team also did a similar deal with Moveable Type last week, so if you prefer that, its just as easy. (although I personally prefer the simplicity and extensibility of WordPress).”\n

    \";}i:52;a:6:{s:5:\"title\";s:18:\"Matt: Wireless BBQ\";s:4:\"guid\";s:45:\"http://photomatt.net/2005/12/20/wireless-bbq/\";s:4:\"link\";s:45:\"http://photomatt.net/2005/12/20/wireless-bbq/\";s:11:\"description\";s:350:\"

    I’m up in Austin to work with Andy for a few days on the new stats system for WP.com. He found this BBQ place (Pokey Joe’s) that offers free wifi with their brisket, and I think I’m in heaven. What more could you want?!\n

    \";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 22:00:46 +0000\";s:7:\"summary\";s:350:\"

    I’m up in Austin to work with Andy for a few days on the new stats system for WP.com. He found this BBQ place (Pokey Joe’s) that offers free wifi with their brisket, and I think I’m in heaven. What more could you want?!\n

    \";}i:53;a:6:{s:5:\"title\";s:50:\"Dougal Campbell: Yahoo! Web Hosting adds WordPress\";s:4:\"guid\";s:74:\"http://dougal.gunters.org/blog/2005/12/20/yahoo-web-hosting-adds-wordpress\";s:4:\"link\";s:74:\"http://dougal.gunters.org/blog/2005/12/20/yahoo-web-hosting-adds-wordpress\";s:11:\"description\";s:432:\"

    \nDouble Yow! Another announcement from Matt: Yahoo! Web Hosting has added WordPress to their offerings. And as an added bonus, they automatically enable the Akismet plugin, which is going to add a wealth of data to that service\'s spam filters.\n

    \";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 19:26:45 +0000\";s:7:\"summary\";s:432:\"

    \nDouble Yow! Another announcement from Matt: Yahoo! Web Hosting has added WordPress to their offerings. And as an added bonus, they automatically enable the Akismet plugin, which is going to add a wealth of data to that service\'s spam filters.\n

    \";}i:54;a:6:{s:5:\"title\";s:17:\"Matt: WP on Yahoo\";s:4:\"guid\";s:44:\"http://photomatt.net/2005/12/20/wp-on-yahoo/\";s:4:\"link\";s:44:\"http://photomatt.net/2005/12/20/wp-on-yahoo/\";s:11:\"description\";s:663:\"

    Check out the new bundling of WordPress with Yahoo Hosting, which is why I was biting my tongue so much last week. \":)\" We’re sitting next to Movable Type on their blog page, but I’m completely comfortable with new users trying out both and making their decision from there. (I often recommend it.) The other part of why this is interesting is the Akismet angle, which I wrote more about here.\n

    \";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 18:09:25 +0000\";s:7:\"summary\";s:663:\"

    Check out the new bundling of WordPress with Yahoo Hosting, which is why I was biting my tongue so much last week. \":)\" We’re sitting next to Movable Type on their blog page, but I’m completely comfortable with new users trying out both and making their decision from there. (I often recommend it.) The other part of why this is interesting is the Akismet angle, which I wrote more about here.\n

    \";}i:55;a:6:{s:5:\"title\";s:28:\"Dev Blog: WordPress on Yahoo\";s:4:\"guid\";s:60:\"http://wordpress.org/development/2005/12/wordpress-on-yahoo/\";s:4:\"link\";s:60:\"http://wordpress.org/development/2005/12/wordpress-on-yahoo/\";s:11:\"description\";s:2568:\"

    As many of you know, we’re constantly tweaking and updating our web hosting page based on feedback we get from you. Well today we’re very excited to announce we’re adding a new host to the page with a familiar name - Yahoo! We’ve been working with the Yahoo Small Business team to create a solution that gives professional bloggers exactly what they want from their hosting providers.

    \n

    When we started, Yahoo asked “What would the perfect blog host do?” and their team has been really amazing in executing on a really kick-ass platform for serious bloggers. It took a little while, but slow cooking makes good eating. (Like WordPress 2.0!)

    \n

    We think the hosting is good for all the baseline features you should expect — tons of storage, bandwidth, Yahoo reliability, etc. (You probably heard all about that in their Movable Type announcement last week.) However we think they’re worth featuring because of three key things:

    \n\n

    Guy Yalif from Yahoo says, “We believe that by adding WordPress’ blogging application to our leading web hosting product, we are providing a top notch, scalable, and reliable solution for less than $12 per month.”

    \n

    We think the above makes a very compelling case for WordPress users to check out Yahoo hosting, and see what we believe is the best WordPress hosting experience on the Web. As always, if you have any feedback on Yahoo or any other host we feature, please let us know.\n

    \";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 17:53:06 +0000\";s:7:\"summary\";s:2568:\"

    As many of you know, we’re constantly tweaking and updating our web hosting page based on feedback we get from you. Well today we’re very excited to announce we’re adding a new host to the page with a familiar name - Yahoo! We’ve been working with the Yahoo Small Business team to create a solution that gives professional bloggers exactly what they want from their hosting providers.

    \n

    When we started, Yahoo asked “What would the perfect blog host do?” and their team has been really amazing in executing on a really kick-ass platform for serious bloggers. It took a little while, but slow cooking makes good eating. (Like WordPress 2.0!)

    \n

    We think the hosting is good for all the baseline features you should expect — tons of storage, bandwidth, Yahoo reliability, etc. (You probably heard all about that in their Movable Type announcement last week.) However we think they’re worth featuring because of three key things:

    \n\n

    Guy Yalif from Yahoo says, “We believe that by adding WordPress’ blogging application to our leading web hosting product, we are providing a top notch, scalable, and reliable solution for less than $12 per month.”

    \n

    We think the above makes a very compelling case for WordPress users to check out Yahoo hosting, and see what we believe is the best WordPress hosting experience on the Web. As always, if you have any feedback on Yahoo or any other host we feature, please let us know.\n

    \";}i:56;a:6:{s:5:\"title\";s:65:\"Weblog Tools Collection: Performancing releases Firefox Extension\";s:4:\"guid\";s:94:\"http://weblogtoolscollection.com/archives/2005/12/20/performancing-releases-firefox-extension/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=533\";s:11:\"description\";s:665:\"

    Performancing releases Firefox Extension Full featured blog editor for Firefox that lets you blog from right within Firefox. Works with Wordpress.com, Wordpress hosted and a multitude of other blogging platforms. Many more screenshots here. Not sure if I will get away from my bookmarklets, but this does look promising.

    \nTechnorati Tags: firefox performancing\";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 15:00:33 +0000\";s:7:\"summary\";s:665:\"

    Performancing releases Firefox Extension Full featured blog editor for Firefox that lets you blog from right within Firefox. Works with Wordpress.com, Wordpress hosted and a multitude of other blogging platforms. Many more screenshots here. Not sure if I will get away from my bookmarklets, but this does look promising.

    \nTechnorati Tags: firefox performancing\";}i:57;a:6:{s:5:\"title\";s:47:\"Weblog Tools Collection: WP Theme: PhoenixTheme\";s:4:\"guid\";s:75:\"http://weblogtoolscollection.com/archives/2005/12/20/wp-theme-phoenixtheme/\";s:4:\"link\";s:60:\"http://feeds.feedburner.com/weblogtoolscollection/UXMP?m=532\";s:11:\"description\";s:387:\"

    WP Theme: PhoenixTheme Sobre and oddly colorful, detailed and pleasing two column theme for Wordpress.

    \nTechnorati Tags: phoenixtheme wordpress theme\";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 09:09:01 +0000\";s:7:\"summary\";s:387:\"

    WP Theme: PhoenixTheme Sobre and oddly colorful, detailed and pleasing two column theme for Wordpress.

    \nTechnorati Tags: phoenixtheme wordpress theme\";}i:58;a:6:{s:5:\"title\";s:15:\"Matt: WP 2.0 RC\";s:4:\"guid\";s:41:\"http://photomatt.net/2005/12/20/wp-20-rc/\";s:4:\"link\";s:41:\"http://photomatt.net/2005/12/20/wp-20-rc/\";s:11:\"description\";s:192:\"

    WordPress 2.0 Release Candidate available — test it while it’s hot, but backup first.\n

    \";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 08:29:34 +0000\";s:7:\"summary\";s:192:\"

    WordPress 2.0 Release Candidate available — test it while it’s hot, but backup first.\n

    \";}i:59;a:6:{s:5:\"title\";s:21:\"Matt: Automattic Beta\";s:4:\"guid\";s:48:\"http://photomatt.net/2005/12/20/automattic-beta/\";s:4:\"link\";s:48:\"http://photomatt.net/2005/12/20/automattic-beta/\";s:11:\"description\";s:520:\"

    Automattic.com is no longer a placeholder, it now has a bit more info about the team behind WordPress.com and Akismet. This is what I’ve been working on since I left CNET. The site is still just a shell though, a lot more tidying up to do there. Your mileage may vary. (Should we call it Beta?) This week is pretty jam-packed with announcements, so stay tuned. \":)\"\n

    \";s:7:\"pubdate\";s:31:\"Tue, 20 Dec 2005 08:28:36 +0000\";s:7:\"summary\";s:520:\"

    Automattic.com is no longer a placeholder, it now has a bit more info about the team behind WordPress.com and Akismet. This is what I’ve been working on since I left CNET. The site is still just a shell though, a lot more tidying up to do there. Your mileage may vary. (Should we call it Beta?) This week is pretty jam-packed with announcements, so stay tuned. \":)\"\n

    \";}}s:7:\"channel\";a:5:{s:5:\"title\";s:16:\"WordPress Planet\";s:4:\"link\";s:28:\"http://planet.wordpress.org/\";s:8:\"language\";s:2:\"en\";s:11:\"description\";s:47:\"WordPress Planet - http://planet.wordpress.org/\";s:7:\"tagline\";s:47:\"WordPress Planet - http://planet.wordpress.org/\";}s:9:\"textinput\";a:0:{}s:5:\"image\";a:0:{}s:9:\"feed_type\";s:3:\"RSS\";s:12:\"feed_version\";s:3:\"2.0\";s:5:\"stack\";a:0:{}s:9:\"inchannel\";b:0;s:6:\"initem\";b:0;s:9:\"incontent\";b:0;s:11:\"intextinput\";b:0;s:7:\"inimage\";b:0;s:13:\"current_field\";s:0:\"\";s:17:\"current_namespace\";b:0;s:19:\"_CONTENT_CONSTRUCTS\";a:6:{i:0;s:7:\"content\";i:1;s:7:\"summary\";i:2;s:4:\"info\";i:3;s:5:\"title\";i:4;s:7:\"tagline\";i:5;s:9:\"copyright\";}s:13:\"last_modified\";s:31:\"Sat, 31 Dec 2005 16:00:36 GMT\r\n\";s:4:\"etag\";s:34:\"\"4a01c5-214aa-43b6ab24;432f77d7\"\r\n\";}',20,8,'',1,'no'),(70,0,'rss_867bd5c64f85878d03a060509cd2f92c_ts','Y',1,'1136045751',20,8,'',1,'no'); +UNLOCK TABLES; +/*!40000 ALTER TABLE `wp_options` ENABLE KEYS */; + +-- +-- Table structure for table `wp_post2cat` +-- + +DROP TABLE IF EXISTS `wp_post2cat`; +CREATE TABLE `wp_post2cat` ( + `rel_id` bigint(20) NOT NULL auto_increment, + `post_id` bigint(20) NOT NULL default '0', + `category_id` bigint(20) NOT NULL default '0', + PRIMARY KEY (`rel_id`), + KEY `post_id` (`post_id`,`category_id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `wp_post2cat` +-- + + +/*!40000 ALTER TABLE `wp_post2cat` DISABLE KEYS */; +LOCK TABLES `wp_post2cat` WRITE; +INSERT INTO `wp_post2cat` VALUES (3,3,2),(4,4,2),(6,5,2),(25,6,7),(13,7,4),(12,8,3),(11,9,1),(14,10,5),(41,11,10),(20,12,6),(18,13,4),(21,14,4),(22,15,4),(23,16,2),(24,17,2),(26,18,3),(33,20,9),(28,19,8),(30,21,9),(31,22,9),(32,23,9),(34,24,9),(35,25,9),(36,26,9),(37,27,9),(38,28,9),(39,29,2),(40,30,4),(43,31,10),(44,32,7),(45,32,10),(49,33,4),(47,34,4),(48,34,10),(50,35,10),(51,35,4),(52,36,2),(53,37,2),(54,38,6),(55,39,4),(56,40,4),(57,41,2),(58,42,2),(59,43,2),(61,45,2),(67,46,4),(63,47,2),(66,48,4),(65,49,2),(68,50,2),(69,51,4); +UNLOCK TABLES; +/*!40000 ALTER TABLE `wp_post2cat` ENABLE KEYS */; + +-- +-- Table structure for table `wp_postmeta` +-- + +DROP TABLE IF EXISTS `wp_postmeta`; +CREATE TABLE `wp_postmeta` ( + `meta_id` bigint(20) NOT NULL auto_increment, + `post_id` bigint(20) NOT NULL default '0', + `meta_key` varchar(255) default NULL, + `meta_value` text, + PRIMARY KEY (`meta_id`), + KEY `post_id` (`post_id`), + KEY `meta_key` (`meta_key`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `wp_postmeta` +-- + + +/*!40000 ALTER TABLE `wp_postmeta` DISABLE KEYS */; +LOCK TABLES `wp_postmeta` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `wp_postmeta` ENABLE KEYS */; + +-- +-- Table structure for table `wp_posts` +-- + +DROP TABLE IF EXISTS `wp_posts`; +CREATE TABLE `wp_posts` ( + `ID` bigint(20) unsigned NOT NULL auto_increment, + `post_author` int(4) NOT NULL default '0', + `post_date` datetime NOT NULL default '0000-00-00 00:00:00', + `post_date_gmt` datetime NOT NULL default '0000-00-00 00:00:00', + `post_content` longtext NOT NULL, + `post_title` text NOT NULL, + `post_category` int(4) NOT NULL default '0', + `post_excerpt` text NOT NULL, + `post_status` enum('publish','draft','private','static','object') NOT NULL default 'publish', + `comment_status` enum('open','closed','registered_only') NOT NULL default 'open', + `ping_status` enum('open','closed') NOT NULL default 'open', + `post_password` varchar(20) NOT NULL default '', + `post_name` varchar(200) NOT NULL default '', + `to_ping` text NOT NULL, + `pinged` text NOT NULL, + `post_modified` datetime NOT NULL default '0000-00-00 00:00:00', + `post_modified_gmt` datetime NOT NULL default '0000-00-00 00:00:00', + `post_content_filtered` text NOT NULL, + `post_parent` int(11) NOT NULL default '0', + `guid` varchar(255) NOT NULL default '', + `menu_order` int(11) NOT NULL default '0', + PRIMARY KEY (`ID`), + KEY `post_name` (`post_name`), + KEY `post_status` (`post_status`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `wp_posts` +-- + + +/*!40000 ALTER TABLE `wp_posts` DISABLE KEYS */; +LOCK TABLES `wp_posts` WRITE; +INSERT INTO `wp_posts` VALUES (3,2,'2005-08-20 22:11:30','2005-08-21 02:11:30','I recently saw smtose.org fall to the briny deep, so here is my new home away from home. I\'ve never considered a webhosting service to be \'sexy\' but damn... they\'ve done a great job impressive even the most skeptical of tech monkeys. I\'m glad I read the ruby-talk post that sung it\'s praises. Despite the good word about textdrive the fact that they weren\'t accepting customers put me here. Anyway, I\'m just posting to say I\'m quite pleased and have a lot of work cut out for me.\r\n\r\nIf you were hosted on smtose.org, you\'ll probably want to get ahold of me about moving your stuff. Which is going to be a nightmare, because I can\'t recover any of the old data (I don\'t think).\r\n\r\nOtherwise, I am now on a service in which I know that it\'s paid for and where everything is. So if you\'re someone who thinks they might be able to mooch free or cheap web hosting off of me, feel free to give me a shout.','So it\'s a whole new world.',0,'','publish','open','open','','so-its-a-whole-new-world','','','2005-08-20 22:11:34','2005-08-21 02:11:34','',0,'http://www.stonecode.org/blog/?p=3',0),(4,2,'2005-08-23 21:17:12','2005-08-24 01:17:12','It would appear that smtose.org is back online. However, it has been officially end-of-lifed and everyone needs to run out of there like their heads were on fire. Well, not quite. I\'m going to redirect your pages here, so that you can know to contact me. I\'m going to lazily attempt to backup your data, too.\r\n\r\nOnce you\'re over here on Stone Code, you can expect stability and predictability. It will be sad to leave the little web host that just won\'t die, but it\'s well worth it to be on a service where everything is actually working.\r\n\r\nI have my web standards solutions book in hand, so the Stone Code Productions website will begin it\'s life tonight. Whether or not I link it to stonecode.org before it\'s done remains to be seen, but expect something far snazzier than any website I\'ve ever made.\r\n\r\nIn developmental news, I am currently working on a bunch of hellish reports for B-Tree Technologies, the company which I do work for New Haven Dental Group under. These reports have inspired me to make a generalized reporting and templating system, which I call Ruport. Mark your calanders, for the first release of this system is set for this Sunday. (2005-08-28)\r\n\r\nAnyway, I\'m off to do some work on the website. Please bear with me as the dust rises and then finally clears.','Back from the briny deep.',0,'','publish','open','open','','back-from-the-briny-deep','','','2005-08-23 21:22:54','2005-08-24 01:22:54','',0,'http://www.stonecode.org/blog/?p=4',0),(5,2,'2005-08-24 00:00:12','2005-08-24 04:00:12','I\'m able to use ruby on this brand spankin\' new host, so I think that my site will be entirely run via ERb. My basic idea is to have a template which covers the entire website so it has a consistant look and feel and just use a simple handler that will fill in the text and graphics specific to each page.\r\n\r\nso stonecode.org/neatorubyapp.cgi?page=AboutUs would grab some specific info for the about us page and fire it back into the template. I\'ll post the sourcecode for it when I\'m done.\r\n\r\nBye bye Mozilla Composer. This bad boy is getting written 100% by hand.\r\n\r\n','Site design preliminaries',0,'','publish','open','open','','site-design-preliminaries','','','2005-08-24 00:03:31','2005-08-24 04:03:31','',0,'http://www.stonecode.org/blog/?p=5',0),(6,2,'2005-08-28 12:18:55','2005-08-28 16:18:55','I wrote this about a week ago, but wanted to run it by Richard Stallman to ensure there wasn\'t anything that misrepresented the Free Software Movement. This is my official propaganda that describes the philosophy behind Stone Code Productions. For those of you who are unimpressed by marketing schemes, be not afraid. Stone Code will also feature a technical dossier which will explain our skills, the types of jobs we\'ll handle, and more nuts and bolts information about the company. However, we need to figure out what those nuts and bolts are going to be before we delineate them! Nevertheless, here is the whimsical and wonderful Statement of Purpose from the little code house that could.\r\n\r\n------------------------------------------------\r\n\r\nWhat is Stone Code?\r\n\r\nFrom ancient times to the present, Stone has represented the\r\neverlasting. Countless companies have tried to solidify their\r\nfortitude through some reference or another to rocks of some variety.\r\nHowever, there is a deeper meaning than that of raw and uncut\r\nstrength. Some of the most well known structures in the world have\r\nbeen carved from the very foundation that makes up our earth. When we\r\nthink of stone, we think of the wonders of the parthenon, the mystical\r\nappeal of legendary stone henge, the central figures of the Zen\r\ngardens of Japan, and the very hearth of the land we stand on.\r\nStrength to carry on throughout the ages, with a certain beauty that\r\ncan only be forged by true artisans, we feel that this concept is a\r\nperfect metaphor for our design goals.\r\n\r\nOf course, Stone is only half of the phrase we have coined for\r\nourselves. The word \'code\' can be quite ambiguous, and it is our goal\r\nas professionals to guide you through the complexity of the world of\r\nprogramming with clarity and satisfaction. The artisans of Stone Code\r\nProductions have acquired a true relationship with the code they have\r\nproduced, and are capable of understanding and improving the code of\r\nothers when the need arises. We invite you to share with us your\r\nvision, so that we may provide durable and beautiful solutions for\r\nyou.\r\n\r\nWhen our conception of Stone and Code merge together, we acquire Stone\r\nCode. The work produced is inherently beautiful because our design\r\nphilosophy centers on form equally as it does on functionality.\r\nBecause our developers place so much pride in their work, it seems\r\nonly natural that it should be free. Do not be alarmed that we will\r\nnot provide work designed for commercial gain, because that is\r\ncertainly not the case. Our work is free in the sense of \"Free\r\nSpeech\", not necessarily \"Free Beer\". We offer our original work\r\nunder the GNU General Public License or any license which is compliant\r\nwith it. Stone Code Productions has it\'s roots in the Free Software\r\ncommunity, and thus will not produce new proprietary software\r\nprojects. However, our skilled team is well versed in the commercial\r\naspects of Free Software, and can aid you and your company to find\r\nprofitable ways to produce and make use of Free Software.\r\n\r\nWe also provide consulting and services to those who are currently\r\nusing or developing proprietary software. We are always happy to help\r\nanyone who has a problem that needs solving. However, we limit\r\nourself to testing, repairing, and maintaining software that is\r\nnon-free due to our philosophy. Therefore if you would like to extend\r\nfunctionality of a system, it would be necessary to find a way to do\r\nthis while preserving the new software\'s freedom. Some may find this\r\na bit strange at first, but we encourage you to talk with us and\r\ndiscuss your options so that we may show you the many benefits Free\r\nSoftware can offer.\r\n\r\nIt is and always will be our goal to give back to the community which\r\nhas provided so much for us. Therefore, Stone Code Productions will\r\nalways provide a portion of it\'s profits to the appropriate\r\ndevelopment groups that have built the tools we use, as well as the\r\norganizations which protect their freedoms. This means that any\r\nclient who does business with our company will be supporting the\r\ncommunity through their business with Stone Code, furthering the\r\nproliferation of software freedom and ensuring that quality software\r\nwill continue to be produced by volunteers and skilled developers\r\nworld wide.\r\n\r\nThrough an active and personalized development process, the artisans\r\nof Stone Code will produce the quality software you need without\r\ncausing unnecessary complications or confusion that are all too common\r\nin the technology field. The result will be something beautiful and\r\nlasting which will bring freedom to both your company and the\r\ncommunity as a whole. Stone Code Productions treats each new project\r\nas a unique venture and proudly adapts to the clients needs while\r\nstill providing a solid service. If you have any software development\r\nor technology work that is in need of an extra hand, feel free to\r\ncontact us at developers@stonecode.org. We often do volunteer work\r\nfor worthy causes, especially those that center around academic growth\r\nor the development of Free Software, so feel free to contact us and\r\ndescribe your situation and we\'ll do our best to accomodate you.\r\n\r\nStone Code means beauty, clarity, and solidarity. Let us work on a\r\nproject for you, and these ideals will shine through in the work\r\nproduced. With beautiful tools, beautiful things can be made. Let us\r\nprovide you with the tools and you will be one step closer to\r\nproviding whatever product or service you specialize in with greater\r\nefficiency, reliability, and appeal.','Statement of Purpose',0,'','publish','open','open','','statement-of-purpose','','','2005-10-06 20:32:39','2005-10-07 00:32:39','',0,'http://www.stonecode.org/blog/?p=6',0),(7,2,'2005-08-30 20:56:30','2005-08-31 00:56:30','Well, after what seemed like an entire summer of hard work, I hastily released Ruport in the worst possible time, the day before I moved back to my University. Unfortunately, some of the effects of the rush I was in could be seen in the quality of the release materials, not necessarily the code, but the announcement on ruby-talk and some of the documents such as the README. I did write a nice manual that anyone who is remotely interested in the Ruby Reports system should check out. The code itself is like a newborn baby... it\'s a bundle of hope and possibly joy for some, but it already has poop filled diapers that need changing and is a little irritable because it\'s teething. Nevertheless, there should be some good things out on the horizon regarding Ruport, so keep your eyes peeled.\r\n\r\nJames and I plan to keep our promise regarding releasing Gambit before the fall. He thinks the last day of summer is the Autumn Equinox (September 21) whereas I thought that we were implying we\'d have it out before Labor Day (September 5). Therefore, my part of Gambit will be done by the 5th, and his by latest the 21st. The release of Gambit should be very exciting, and if anyone wants to see a summary of our codefest this summer, there is a great \r\narticle on ruby-talk which James wrote. You need not be an uber technophile to appreciate this article, surprisingly but of course it does help!\r\n\r\nI will be designing the Stone Code website this week and next. I will release the source code to the Ruby engine that runs it when it is built. If anyone hosted here on Stone Code would like me to install it for them when I get it built, just let me know.\r\n\r\nWell, that is the news for now. Starting tomorrow I have to \'learn\' how to code again. Stupid school, always getting in the way of my real projects :) ','Ruport\'s Initial Release',0,'','publish','open','open','','ruports-initial-release','','','2005-09-02 01:09:18','2005-09-02 05:09:18','',0,'http://www.stonecode.org/blog/?p=7',0),(8,2,'2005-08-31 18:03:48','2005-08-31 22:03:48','If you read my Statement of Purpose for this new venture I\'ve started, you\'d know that Stone Code Productions plans on making contributions to the community whenever possible. One great way to do this is through Dreamhost themselves. They agree to match whatever donations their customers make. Money is a bit tight currently, but I decided to make Stone Code\'s first contribution. I donated $10 dollars to Wordpress, the people who build the excellent open source software this weblog runs on. I also donated $10 to the American Red Cross to help towards the support of those effected by the hurricane that swamped the Gulf Coast. As of now, I am aware that all of you who are hosted on Stone Code are college students who probably aren\'t in much better of a money situation than me, but if you enjoy the services I\'ve been providing to you for free and would like to help me help the community, you can stop by my apartment with some cash and I\'ll make the donation right there on your behalf (which will be met by Dreamhost). It could be any random amount, and I\'d appreciate you helping Stone Code achieve one of it\'s goals as well as supporting these worthy causes. There are new charities every month (I think), and I will be sure to keep you posted.\r\n\r\nAny questions? Email me: gregory.t.brown at gmail dot com','Charitable Donations',0,'','publish','open','open','','charitable-donations','','','2005-09-02 01:08:59','2005-09-02 05:08:59','',0,'http://www.stonecode.org/blog/?p=8',0),(9,2,'2005-09-01 20:59:31','2005-09-02 00:59:31','Apparently, Microsoft is trying to make using C as hard as possible to use in Visual Studio .NET to encourage people to turn to C++ and C#\r\n(Or at least that\'s my assumption). \r\n\r\n For the *nix savvy, I post this little tutorial on just how to get VS.Net to run a hello world program so that you can laugh ( a lot! ) . \r\n\r\n For my Intermediate C class, I post this so that you can get VS.net working though I highly recommend doing some research on GVim, Cygwin, and gcc to save yourselves some serious headaches down the line. \r\n\r\n Nevertheless, let\'s begin the torture... err... tour.\r\n\r\nIf you have trouble or want to mention something, feel free to leave a comment.\r\n\r\n','C in Windows Nightmare Explained',0,'','publish','open','open','','c-in-windows-nightmare-explained','','','2005-09-01 21:23:53','2005-09-02 01:23:53','',0,'http://www.stonecode.org/blog/?p=9',0),(10,2,'2005-09-05 11:04:06','2005-09-05 15:04:06','Because this is relevant to the type of work I do I decided to syndicate this brief book review from my personal weblog:\r\n\r\n\" The book I read was The Anarchist in the Library by Siva Vaidhyanathan and it was incredible. It talked about the nature of anarchism and it’s influence on society. (That’s small a anarchism, not necessarily the brand associated with the Anarchist Movement, but just the nature of unestablished action ) It talks about the oligarchy’s tendency to try to limit the openness of society in support of authority and state, and even their various attempts to create the illusion of ‘panics’ which make such circumventions of freedom seem just or reasonable. Siva provides a pragmatic analysis of the situation, and is not afraid to admit weaknesses or problems existent within the various Anti-Establishment movements (to include that of the Libertarians) and though I may not share his moderate view, I respect this in his presentation. He is clearly in favor of an open society but is willing to consider the claims and concerns of the other side, which makes it more likely for people to take him seriously. This book was very high level, and though it does not limit itself to a particular niche, you may find yourself struggling in places if you are not a technophile or a fan of the sciences. Nevertheless, it was an amazing read despite the fact that it ate up my whole day. \"\r\n\r\nSiva responded on my personal blog which was surprising. So i figured it couldn\'t hurt to pimp his wares over here on the professional blog, too.\r\n','Anarchist in the Library (Review)',0,'','publish','open','open','','10','','','2005-09-05 11:08:38','2005-09-05 15:08:38','',0,'http://www.stonecode.org/blog/?p=10',0),(11,2,'2005-09-13 13:18:30','2005-09-13 17:18:30','Again, Community, Community, Community for Stone Code.\r\n\r\nI\'ve started a ruby programmers group here at the University of New Haven to continue to reach out the community and help people learn about concepts such as agile design and pragmatic programming. With the help of James Edward Gray II, hopefully we\'ll put together a great program for the students here.\r\n\r\nhttp://stonecode.org/organizations/unh.rb/','UNH.rb',0,'','publish','open','open','','unhrb','','','2005-10-26 02:53:31','2005-10-26 06:53:31','',0,'http://www.stonecode.org/blog/?p=11',0),(12,2,'2005-09-20 15:58:29','2005-09-20 19:58:29','So, it appears that I\'ve taken it upon myself to fight for yet another community\'s rights.\r\n\r\nThis time, it was the University of New Haven student body. Our network was out for over 2 hours consistantly and many more smaller increments throughout the weekend, causing me to miss both of my scheduled UNH.rb online meetings as well as experience all sorts of problems that someone as reliant on the Internet as I am. \r\n\r\nFirst I will offer my account of the situation in plain terms so those of you who scratched your head at the Network Status message can get an idea of what happened in plain terms.\r\n\r\nA critical network component failed, causing the entire network to experience an outage. There was a backup system in place, but it did not switch over correctly due to a malfunction. The department identified the problem with this piece of hardware earlier, and the replacement is on the way, but it failed before they could replace it. Though it normally would not take a long time to replace this device in an emergency, there were some scheduling conflicts that delayed the process. \r\n\r\n\r\nNow if you\'re interested in the whole story, read on.\r\n\r\nInstinctively I sent a somewhat hostile message to the Director of Networking as well as the student body. The reason for this was a combination of frustration and the fact that the explanation the student body recieved was insufficient and unclear as to what the actual problem was. I highly doubt anyone understood a word in it, because it was in a jargon only network nerds (such as myself) can or should comprehend. Therefore, I needed to elicit a response which would clarify what actually happened.\r\n\r\nImmediately after it was sent, I recieved over 10 emails and instant messages thanking me for putting in words the frustrations of the student body.\r\n\r\nAlan MacDougall was the first to address my concerns. We had a rather extensive dialogue in which I learned what the actual situation was, the actions of the department throughout the situation, as well as the stance taken by the department about future issues. Unfortunately, Alan declined permission for me to show you what he said, which may have been an appropriate action if he intended to protect the reputation of his department, or not have it cast in a skewed light.\r\n\r\nHowever, here is essentially the jist of what was said. The issue was primarily one in getting a technician or administrator to the University in a timely fashion to resolve the problem. There was trouble contacting Greg, the Director of Networking, and this led to the disorganization which increased the delay in fixing the problem.\r\n\r\nThis problem was never supposed to happen in the first place, because the system was SUPPOSED to go to a backup one if it failed. This may be part of the reason why it was not watched as closely as it should have been, though Alan did not comment on this.\r\n\r\nAlan cited that 87 hours of downtime was not unheard of or uncommon in businesses. This is 99% reliability, which may seem like a big number, but in real terms, clearly is not.\r\n\r\nFor this, I refer you to Technet:\r\n\r\n\r\n\r\n
    \"In an organization that is operational 24x7x365 (24 hours a day/seven days a week/365 days a year), systems that are 99 percent reliable will be unavailable, on average, 87 hours (3.5 days) every year. Moreover, that downtime can occur at unpredictable timesâ€â€possibly when it is least affordable. It is important to understand that an availability level of 99 percent could prove costly to your business.\r\n\r\nInstead, the percentage of uptime you should strive for is some variation of 99.x percentâ€â€with an ultimate goal of five nines, or 99.999 percent. For a single server in your organization, three nines (99.9 percent) is an achievable level of availability. Achieving five nines (99.999 percent) is unrealistic for a single server because this level of availability allows for approximately five minutes of downtime per calendar year. However, by implementing fault tolerant clusters with automatic failover capabilities, four nines (99.99 percent) is achievable. It is even possible to achieve five nines if you also implement fault tolerant measures, such as server-class hardware, advanced storage solutions, and service redundancy.\"\r\n
    \r\n\r\n\r\nOriginal link\r\n\r\nSo if Alan\'s indication is that we can expect 99% reliability, that mean you might expect about 1.5 hours of downtime a week.\r\n\r\nHowever, before I make it seem that the Tech department is evil and out to screw University Students, let me clarify:\r\n\r\nIt\'s not that they aren\'t doing their job, it\'s not that they don\'t care. From talking to both of these individuals, it\'s obvious that our needs are important to them. However, you may not (and probably have not) realized that from the \"Network Status\" which tend to be terse and overly complex at best. \r\n\r\nMr. Bartholomow also declined permission for me to quote him directly, but indicated that the messages sent to the students are less informative than they can be, because it is enormously difficult to find the appropriate wording for such things. This certainly has some truth to it. \r\n\r\nMr. Bartholomew asserted that there was indeed a scheduling conflict involved and that it would be dealt with in due course.\r\nHe also informed me that the new hardware will be redundant so that we will not have this same problem again.\r\n\r\nHe indicated that if it were possible with the current funding to have a 24/7 staff, they would have one. However, again it boils down to \'not enough money to go around\' which forces our University\'s departments to make due with what they have.\r\n\r\nI am uneasy with this, and really hope that the University takes this seriously and gets the Technology department (or whatever their official name is) the money they need to achieve 99.99% uptime, which is the goal we should be striving for. \r\n\r\nOur technology department is full of capable and hard working people. It may just be that these people do not place enough emphasis on making it clear to the students that they care. The best way for them to do so is give simple, honest, complete explanations to the student body and the university as a whole. Most of you don\'t know what a \'blade\' is, but everyone can understand that they were having trouble contact the right people to fix the problem, that there was supposed to be a backup system that failed, and that they\'re working on prevention methods to keep this from happening again. But you wouldn\'t know that from the email they sent you, and therein lies the problem.\r\n\r\nIt is our responsibility as students to ask the hard questions when necessary, to demand clear and truthful explanations of problems. Sometimes, even we forget that. So it\'s not surprising that maybe the tech department got a little laxed with their communications regarding a very serious problem.\r\n\r\nIt is my hope that they use this as a learning experience, and that the student body will be more vigilant about their rights and continue to demand that our departments respect them. It is their duty to serve our needs, and though they may be doing a great job of it working on this behind the scenes, they haven\'t made it clear to us, and they certainly should.\r\n\r\nIt is also my hope that the University opens up a dialogue with the tech department that involves a REAL evaluation of costs involved in providing a reliable network. It seems like we\'re trying to squeeze blood from a stone, and from my experience, it ends up costing more and being less effective in the long run to use an incomplete solution as a means to an end.\r\n\r\nBottom line... Students need to speak up when they think something is fishy, The tech department needs to address the real problem in plain terms whenever it shows up so that the University community understands, and the University itself needs to open the pocketbook a little wider so that we can be ensured reliable connectivity to the Internet, as it is absolutely essential and holds a higher priority in most of our lives than many other \"improvements\" that can be made to the campus.\r\n\r\nBut this is just a rant from an angry coder who lost the internet for a couple hours. Don\'t take me too seriously.\r\n\r\nI hope this offers an alternate view of the situation for anyone interested though.\r\n\r\nI appreciate Alan and Greg\'s efforts to clarify the situation with me, and I hope that I have explained it in a way you\'ll be able to understand too. Of course, this is only my account, so do not take it as absolute or fully correct.\r\n\r\nThat\'s the end of this chapter... Hopefully we\'ve all learned something that will improve our community for the better.\r\n\r\n\r\n\r\n','The University of New Haven Network Outage',0,'','publish','open','open','','the-university-of-new-haven-network-outage','','','2005-10-02 12:40:00','2005-10-02 16:40:00','',0,'http://www.stonecode.org/blog/?p=12',0),(13,2,'2005-09-28 03:14:20','2005-09-28 07:14:20','It\'s safe to say that Ruport is starting to get \'Down with the Sickness\'.\r\n\r\nScheduled release date for Ruport 0.2.0 is Friday, October 7th. It will feature full unit tests for all implemented features, a fix to the two executable hack(ruport and ruport.rb), the new file gizmo\'s and report generation automation, exception handling and logging for core ruport issues as well as some custom exception classes to be handled in the templates.\r\n\r\nI\'d love to go into implementation details of the above mentioned features, but a few I still have not even dreamed up yet, so you\'ll just have to keep an eye on the CVS if you\'re interested.\r\n\r\nBelow is a running ruport template which saves a data query with the columns seperated by |\'s to a file called foo.txt in the reports directory OR to a file specified on the command line, and also emails the report to me.\r\n\r\n@file ||= \"reports/foo.txt\" \r\n\r\n@mailer.recipients += \"gregory.t.brown@gmail.com\"\r\n\r\nselect \"* from ruport_test where day_of_month > 10\" do |r|\r\n @report += r.join(\" | \") + \"\\n\"\r\nend\r\n\r\ngenerate_report\r\n\r\n\r\nYou\'re probably wondering, where\'s all the code? That\'s getting to be a common question with ruport templates, who leave all the frameworking behind the scenes where it should be :)\r\n\r\nBy the way, in 0.2.0, even generate_template will be behind the scenes, making this example a line shorter!','Ruport 0.1.9: The Bleeding Edge',0,'','publish','open','open','','ruport-019-the-bleeding-edge','','','2005-09-28 03:25:03','2005-09-28 07:25:03','',0,'http://www.stonecode.org/blog/?p=13',0),(14,2,'2005-10-01 05:07:48','2005-10-01 09:07:48','So... Here\'s the good news. \r\n\r\n Getting Rails to run on stonecode took about a quarter of a minute.\r\nI poured through the first two chapters of Agile Web Development with Rails with lightning speed to ensure that things are working.\r\n\r\nTherefore, for those of you who are waiting on the Stone Code Web Application, I\'ve now taken my first real step towards building it.\r\n\r\nIt was a baby step, because it\'s 5 am. But if I wake up with motivation in a few hours, there is a chance you might see a partial prototype by the end of the weekend.\r\n\r\nI must say, I\'ve never been more pleased with a service than I am with Dreamhost.\r\nThey\'re incredible.\r\n\r\n\r\n\r\n','Duke Nukem Forever -- Err... The Stonecode Website Status Update',0,'','publish','open','open','','duke-nukem-forever-err-the-stonecode-website-status-update','','','2005-10-02 12:40:13','2005-10-02 16:40:13','',0,'http://www.stonecode.org/blog/?p=14',0),(15,2,'2005-10-04 04:12:06','2005-10-04 08:12:06','The Stone Code Store is now open! We are currently in the business of selling weapons that will aid you in the killing of cute animals. See for yourself here:\r\n\r\nhttp://stonecode.org:3000/store\r\n\r\nBefore you take an axe to my throat, realize that this of course, is in jest. Not only is the store incomplete, it\'s entirely fictional, and is merely a slightly more colorful example of the Depot tutorial in Agile Web Development with Rails. \r\n\r\nI just figured I\'d give those of you who read this a quick update on my Rails learning, which is essential for my building of the Stone Code Web Application that some of you are itching to usurp from me and use on your accounts. I have just completed chapter 7, and all is good in Railsville. \r\n\r\nExpect the Stone Code Web Application before Thanksgiving. That\'s the best estimate I can make presently. \r\n\r\nHowever, I do plan to release Ruport 0.2.0 this Friday, so needless to say, Stone Code is busy as a beaver... maybe even two beavers. ','Stone Code Store',0,'','publish','open','open','','stone-code-store','','','2005-10-06 10:37:47','2005-10-06 14:37:47','',0,'http://www.stonecode.org/blog/?p=15',0),(16,2,'2005-10-04 05:06:43','2005-10-04 09:06:43','In the month of september, I had 10,846 page requests on all of Stonecode\'s services.\r\n\r\nOf those, 6781 were Firefox. Users of IE, Camino, and yes, even Safari, Get with the picture!\r\n\r\n\"\"\r\n\r\nAnd now onto our OSes, You can see that when you combine OS X and other forms of Unix, Stone Code users and their audience are trumping Windows like mad.\r\n\r\n\"\"\r\n\r\nWhoo :)\r\n\r\n','The Fire Spreads On.',0,'','publish','open','open','','the-fire-spreads-on','','','2005-10-04 05:07:58','2005-10-04 09:07:58','',0,'http://www.stonecode.org/blog/?p=16',0),(17,2,'2005-10-06 20:28:22','2005-10-07 00:28:22','http://www.stonecode.org:3001/fortune\r\n\r\n2 Lines of Ruby on Rails Code + 14 Lines of RHTML =\r\n N - 1 bored CS students in a C Lecture right now.\r\nWhere I am that one entertained student, which of course, has nothing to do with C :)\r\n\r\n','AJAX in your eyes!',0,'','publish','open','open','','ajax-in-your-eyes','','','2005-10-06 20:33:36','2005-10-07 00:33:36','',0,'http://www.stonecode.org/blog/?p=17',0),(18,2,'2005-10-09 13:08:45','2005-10-09 17:08:45','I\'ve rather selfishly donated $25 to the organizers of RubyConf 2005, seeing as I\'ll be attending it this year :)\r\n\r\nAll kidding aside though, RubyCentral has provided a ton of wonderful resources to the Ruby community, including such things as the codefest grant which allowed me to go out and work with James Edward Gray II on Gambit this past summer.\r\n\r\nSo, if your interested in keeping an organization that promotes people like me to \"Keep it Real\", please consider donating at the site below.\r\n\r\nhttp://www.rubycentral.org/pledge/\r\n\r\nA note for those hosted on Stonecode.org, especially organizations, you\'re getting FREE hosting ya bums! Kick some cash my way and actually help something! ;) Again, if you\'re using Stone Code for something and have even a few extra bucks to spare, get ahold of me and I\'ll lump your cash together and fire it out to this great cause.\r\n\r\nOkay, I\'m done soliciting. For now.','RubyCentral Donation',0,'','publish','open','open','','rubycentral-donation','','','2005-10-09 13:10:00','2005-10-09 17:10:00','',0,'http://www.stonecode.org/blog/?p=18',0),(19,2,'2005-10-09 22:09:36','2005-10-10 02:09:36','Yes!\r\n\r\nhttp://project.ioni.st/post/269#post-269\r\n','',0,'','publish','open','open','','19','','','2005-10-09 22:10:13','2005-10-10 02:10:13','',0,'http://www.stonecode.org/blog/?p=19',0),(20,2,'2005-10-13 12:09:54','2005-10-13 16:09:54','I missed a flight this morning and now have to leave Dallas via a standby-status flight. I expect to reach California sometime tonight, but I don\'t know if it will be in time for the dinner.\r\n\r\nSee ya when I see ya...\r\n\r\nGreg\r\n\r\n(Posted by James Edward Gray II.)','California, The Slow Way',0,'','publish','open','open','','california-the-slow-way','','','2005-10-14 18:40:17','2005-10-14 22:40:17','',0,'http://www.stonecode.org/blog/?p=20',0),(21,2,'2005-10-14 14:05:33','2005-10-14 18:05:33','Francis Hwang gave a great first talk on unit testing and talked a lot about how to use mock objects to increase the speed of unit tests. He explained a bit about MockFS, including the crackrock feature that redefines Fileutils and the like. I personally think that\'s cool as hell but it does seem like it could be incredibly dangerous.\r\n\r\nHe mentioned the fact that Lafcadio completly mocks MySQL to the point where you can test it without even having a database installed. I am absolutely sure that I need to steal this functionality for Ruport :)\r\n\r\nHe used easyprompt as example of mock user IO. Meh. I think we handle it better(support direct StringIO) in HighLine, but that\'s my ego talking.\r\n\r\nAnyway, you can check out the code examples from this first talk here:\r\nhttp://fhwang.net/top_to_bottom/\r\n\r\nDHH really made it clear that he pretty much disapproves of mocking in unit tests and talked about how all the Basecamp units are gone in 60 seconds or less using a \'real\' database. \r\n\r\nMeh, at least the ghost of Tim Toady is alive and well in this room.\r\n\r\nOn a side note... sitting next to me right now is Matz. That\'s kind of intimidating. :)\r\n\r\nAnd... the open-uri talk is starting, so... I\'ll have more later.','Unit testing talk .',0,'','publish','open','open','','unit-testing-talk','','','2005-10-14 17:08:08','2005-10-14 21:08:08','',0,'http://www.stonecode.org/blog/?p=21',0),(22,2,'2005-10-14 17:44:42','2005-10-14 21:44:42','In the Open-URI talk, Akira Tanaka gave some great insight into good design. In fact, it seemed as if this was the central point of the talk, rather than open-uri itself. This is not due to the fact that open-uri is not worth much attention, but rather that it is designed so simply, that everything it does just \'makes sense\'. Through his talk, Akira showed us how to follow the DRY methodology and the \"no configuration is good configuration\" concept to produce something simple yet elegant.\r\n\r\nThe JRuby talk was a bit over my head as I\'ve closed my eyes to Java and never want to look back. There was some quite interesting discussion of the limitations of an implementation within the JVM (such as stack depth and speed), but nevertheless, for those who need to sneak Ruby through the system, I\'m sure this talk helped them get up to speed, as well as point out any gotchas that might be of note. I think it\'s pretty neat that they\'re going to continue to try to make mainstream ruby apps run under JRuby. \r\n\r\nWell... the YARV talk is downright hilarious. So I\'m going to put my attention into it right now. More on that (and a top secret Ruport discussion / hackfest? ) in a bit.\r\n\r\n','Open-URI / JRuby',0,'','publish','open','open','','open-uri-jruby','','','2005-10-14 17:44:42','2005-10-14 21:44:42','',0,'http://www.stonecode.org/blog/?p=22',0),(23,2,'2005-10-14 18:38:58','2005-10-14 22:38:58','The YARV talk was excellent. Koichi Sasada is one of the most entertaining people I\'ve seen at this conference. It seems as if YARV is getting really close to being \'ready\'. Of course, he\'s got plenty of hurdles to cross before it\'s 100% there. The best way I can summarize the talk is that YARV is way faster than the current interpreter and that it pulls some really neat tricks as far as the way it handles ruby code. Of course, it\'s going to need to handle c extensions, which means it\'ll need to be able to run mkmf.rb or implement some equivalent, which is cannot yet do.\r\n\r\nTwo very cool things to be taken out of this talk:\r\n\r\n\r\n1. ) http://www.atdot.net/yc/ : \r\n\r\nA CGI based program that\'ll compile your ruby and then decompile it into YARV bytecode. (Which will let you see how Sasada is doing a good job of making assembly language look as non scary as it can be)\r\n\r\n2.) His demonstration showed that regular YARV is twice as fast as the ruby C interpreter, and the complicated but optimized YARV runs 4 times as fast.\r\n\r\nA 400% speed increase will put Ruby ahead of Python and Perl... giving us Rubyists even more bragging rights :)\r\n\r\nHere comes Reimplementing Ruby in Ruby. Sounds scary :)\r\n\r\nMore on this and the \"Spooky\" Ruport plans later. \r\n\r\n','YARV!',0,'','publish','open','open','','yarv','','','2005-10-14 18:40:52','2005-10-14 22:40:52','',0,'http://www.stonecode.org/blog/?p=23',0),(24,2,'2005-10-14 19:31:35','2005-10-14 23:31:35','So... the MetaRuby talk was quite interesting.\r\n\r\nThe basis of the project seems quite reasonable. Let\'s get Ruby the hell out of C so that it can be understood better! Makes sense to me, I have never looked at the source code for the interpreter but I imagine there is some voodoo in there that would look far nicer in Ruby.\r\n\r\nIt seems like this project has two major caveats. 1) It hinges on a lot of other dependencies... understandably. 2) It\'s a HUGE project.\r\n\r\nHopefully Seattle.rb will be able to pull together the resources necessary to do this, because it\'s really cool. The talk was interesting because it let us get an insight into what exactly is going on underneath the hood in Ruby and how it might look with a bit of a facelift via a language change.\r\n\r\nIt seems to me like implementing the system in itself will be enormously complex, but I guess that\'s what makes it so damn interesting.\r\n\r\nFor those of you who are interested, after the roundtable discussion tonight I\'m going to be hacking on Ruport.\r\n\r\n I\'d love to meet up with some of you that are interested in this project to get suggestions and pretty much steal your ideas. I want Ruport to be great, but I need to know what people think is cool and what people think sucks to do that, so tonight is as good of a night as any. If you\'d like to code along with me, or just offer some helpful hints, you\'re welcome to. I\'m guessing i\'ll start around 10pm, somewhere at the hotel.\r\n\r\nIf you\'re interested, email me at: gregory.t.brown@gmail.com or come find me later.\r\n\r\nNow I\'m off to get lost using the buses. RubyConf 2005 Day 1 has rocked!','MetaRuby Talk, and shady dealings',0,'','publish','open','open','','metaruby-talk-and-shady-dealings','','','2005-10-14 19:33:09','2005-10-14 23:33:09','',0,'http://www.stonecode.org/blog/?p=24',0),(25,2,'2005-10-15 13:29:51','2005-10-15 17:29:51','I missed part of the Roundtable last night because I was taking a cab back from Downtown San Diego after walking to Coranado Island (occasionally catching a bus). I simply needed to see the Pacific Ocean and it ended up costing me almost $40 and about 5 hours of time, but I got there :)\r\n\r\nWhen I came back, I got into the room to see Matz sitting there answering a barrage of questions. It seemed as if that was a scary position to be in, but that he was enjoying it. Someone asked if there was anything from Perl 6 that he\'d consider stealing, and to get a rouse out of the room, Matz just said one word \"Arrow\".\r\n\r\nIt was funny to hear Austin Zielger asked \"when we can expect Marshalling for all objects\" as if he was saying \"When am I going to get my money\" while holding a tire iron. Matz laid it out straight, we\'ll never see it. There is just no clear way to deal with marshalling certain things. However, the biggest concerns were things such as procs and closures and singletons, which Matz said would be addressed in 2.0\r\n\r\nThen the question of the evening (IMHO) was posed by Hal Fulton. He asked if Matz though Ruby 2.0 is too ambitious to be attainable. Matz said something to the effect that 2.0 might be something like Perl 6, which caused a silence to fall over the room. But Matz reassured us that we\'d all keep working hard and we\'d get there \"eventually\".\r\n\r\nAll in all, it was an exciting evening.\r\n\r\nThe No Clergy talk was pretty interesting. Kevin Baird demonstrated how his software worked, and I thought the idea was great. A democratic orchestra? For those of you who aren\'t familiar with No Clergy, it\'s basically this polling system that takes a bunch of traits and allows the audience to rate them and then does some Markovian transformations on the music to generate sheets that reflect the popular opinion. This is done actively throughout the performance.\r\n\r\nThe relevance to ruby was the fact that this software was originally written in Python and was refactored into Ruby. He explained the system a bit, and it certainly shows promise.\r\n\r\nI found his webpage for the software, but it\'s quite limited in information. Nevertheless:\r\nhttp://nibbler.med.buffalo.edu/noclergy/?li=yes\r\n\r\nSome interesting points to note are that Kevin threw the question regarding packaging (Gems vs. Debian) at the audience. Thankfully, someone suggested \"why not both!?!\", Kevin admitted laziness, which of course elicited the response \"script it away!\"... I\'m sure that a rake task could handle this quite quickly. \r\n\r\nSomeone also mentioned that there is an automated gem -> ebuild program for gentoo. A quick googling couldn\'t locate this, but as a regular Gentoo user myself, I\'m definitly going to have to find it.\r\n\r\nThere was a talk about a Refactoring browser for Ruby. Meh. I feel like vim takes fine care of me. Though I think such tools would be great for people who enjoy FreeRide and the like.\r\n\r\nOkay, so thats that. Marine Bio talk is going on right now. He is finally getting into the Ruby, there was quite a bit of background information which I admittedly did not catch all of. But I\'ll be sure to comment on the ruby in a bit.\r\n\r\nExtracurricular Activities:\r\n\r\n1) Ruport\r\n\r\nI did not end up doing much work on Ruport because I felt like I was going to die after the walk in and around Coronado but today and tonight will certainly give rise to some Ruport news.\r\n\r\nThere is a certain feature everyone wants that I was saying no to for a while but have caved. You bastards are getting charting support!\r\n\r\n2) Go. \r\n\r\nI found a guy playing Go on his machine. If anyone at the conference brought a board with them and wants to play, I am so down for it! Email me: gregory.t.brown at gmail dot com\r\n\r\n\r\n','Roundtable / No Clergy / Ruport / Coronado / Go',0,'','publish','open','open','','roundtable-no-clergy-ruport-coronado-go','','','2005-10-15 13:30:55','2005-10-15 17:30:55','',0,'http://www.stonecode.org/blog/?p=25',0),(26,2,'2005-10-15 17:37:38','2005-10-15 21:37:38','The presentations have kicked some serious ass today!\r\n\r\nBrent Roman explained an interesting application of Ruby in embedded devices for a scientific application. It was interesting to see that it is possible to use Ruby in a real time environment and he accomplishes this through some tricky communication with embedded controllers that use C firmware which is configured by Ruby.\r\n\r\nHe made an interesting point about some issues with Mutex, such as the ability for a thread to come along and pretty much highjack the lock, or something to that effect. This was a bit over my head, honestly.\r\n\r\nOne of the coolest things was that he explained how he managed to get the ruby interpreter to be incredibly small (< 2 MB total ) by going through the makefile and dropping things such as tk, curses, and the like from the standard lib.\r\n\r\nThis sounds like a lot of fun, and of course, in his case, a necessary condition for certain applications.\r\n\r\nThe PDF::Writer presentation was yet another mind blowing experience. Austin Ziegler started by generating his slideshow using PDF::Writer. It was using an upcoming presentation system, which has not yet been implemented in a major release.\r\n\r\nHe showed all sorts of neat little tricks with PDF::Writer including SVG support. He also managed to show us some of the areas of PDF::Writer that still need work.\r\n\r\nOver lunch myself and some others talked to him about charting and he let us know that someone offered some contributions. With the demand for charts in Ruport, if these contributions come later rather than sooner, it\'s likely I will contribute some charting functionality as well, though it might be a little bit coupled to Ruport.\r\n\r\nAustin talked fairly extensively about good API design and suggested that it\'s a matter of feeling out a piece of software until it feels right and then being consistent. Between PDF::Writer and open-uri, I am getting more and more inspired to provide a beautiful API for Ruport, though this probably means a fairly heavy amount of refactoring.\r\n\r\nFor those who are curious, Ruport is finally passing it\'s shoddy unit tests again after a HORRIBLE mishap (details later). These tests are going to be completely rewritten using some of the ideas and methodologies I\'ve picked up out here.\r\n\r\nJim Weirich has the floor now for DSLs. I\'ll have my take on this talk and the AWESOME ZenHacks talk later today...\r\n','Marine Bio (Ruby Embedded) / PDF::Writer ',0,'','publish','open','open','','marine-bio-ruby-embedded-pdfwriter','','','2005-10-15 17:38:24','2005-10-15 21:38:24','',0,'http://www.stonecode.org/blog/?p=26',0),(27,2,'2005-10-15 18:46:51','2005-10-15 22:46:51','Well... here comes the promised update before the next talk begins.\r\n\r\nThe ZenHacks talk was definitely exciting. They went through the whole suite of oddities from Ruby2C to Ruby2Ruby and warned us of the amazing amount of Dragons that lie beneath. There were two projects that tied into this that appeared to be very useful though, these two are RubyInline and ZenTest\r\n\r\nRubyInline basically makes C appear to be interpreted by letting you literally drop it in place alongside Ruby code. It handles all the compiling and linking for you internally, leaving no hassle for you.\r\n\r\nZenTest generates missing methods from tests, and missing tests from methods. The coolest thing in the package is unit-diff, which prevents the whole \r\n\r\n[100linesofgoodhtml] expected but got [100linesofbadhtml].\r\n\r\nIt produces diff files showing exactly what was different between the two :)\r\n\r\nFor the DSL presentation, Jim Weirich brought us through the dredges of ruby\'s voodoo in such things as Constant / Method missing to implement simple to write and clean domain specific languages.\r\n\r\nHe implemented this killer Rubik\'s cube application which ran in IRb and implemented a DSL that interpreted Rubik notation and even offered a nice colored display. A quick google implies it\'s not just sitting out there online for our consumption, but if you can find it, check it out because it was downright awesome. \r\n\r\nAnyway, Karlin is now talking about Systir, which I will have more on later.','Zenhacks / DSLs',0,'','publish','open','open','','zenhacks-dsls','','','2005-10-15 18:46:51','2005-10-15 22:46:51','',0,'http://www.stonecode.org/blog/?p=27',0),(28,2,'2005-10-18 02:06:21','2005-10-18 06:06:21','The conference was amazing. Everyone who participated was so great to be around. I even managed to rustle up some Ruport supporters :)\r\n\r\nThe keynote address, though it bordered on a flamewar, showed some great ideas from Dave Thomas regarding the lambda dillema...\r\nsuggesting def (n = 5) ... end, which to me seems great (No arrows!) Of course, david black\'s suggestion of lambda (x =2) ... end was also quite compelling. An interesting compromise was proposed that the latter be a closure and the former not, which is quite interesting.\r\n\r\nMatz\'s theme was \"wild and crazy\' ideas, and I\'m sure you can find a summary elsewhere regarding the specifics. One great thing from the conference were the people using SubEthaEdit to do some collaborative journalism. These posts are on the feed, so I\'m sure you\'ve probably seen them already.\r\n\r\nThe final panels on rails and the long tail and damagecontrol were interesting, but in my not at all humble opinion, paled in comparison to the earlier talks. DamageControl looked promising but there were a lot of technical difficulties and pinholes that got in the way of an otherwise good presentation. However, DHH\'s workshop on \"What\'s new in Rails 1.0\" made up for this, and pretty much got us up to speed on what to expect. I\'m excited about schema support and the ability to freeze the gems, as these two features seem really handy.\r\n\r\nFrom what I heard, the continuations talk was brain melting but awesome.\r\n\r\nI\'m proud to say that I\'m going to be doing my best to attend the monthly NYC.rb meetings from now on. Francis Hwang and Matt Pelletier seem like great guys, so I\'m psyched to get some Ruby and Rails goodness from their groups.\r\n\r\nTo all the organizers, presenters, and attendees of RubyConf 2005, thanks a ton for a great conference and see you next year!\r\n\r\n\r\n\r\n','Final Take: RubyConf 2005',0,'','publish','open','open','','final-take-rubyconf-2005','','','2005-10-18 02:06:21','2005-10-18 06:06:21','',0,'http://www.stonecode.org/blog/?p=28',0),(29,2,'2005-10-20 01:33:09','2005-10-20 05:33:09','So, the RubyConf has set my mind on fire and I don\'t think I can put it out. There were so many wonderful technologies out there, so many great ideas, so many cool people. Anyway, that having been said... I have some bad news. Aside from my weekly hours at work and the UNH.rb meetings (next one will be next week sometime), I am taking a few weeks vacation from the Lady in Red.\r\n\r\nUnfortunately, as much as I love hacking Ruby almost as much as certain carnal desires, my grades have really taken a punch in the stomach due to my involvement with most anything BUT my homework for Intermediate C, Databases, Physics, and even my favorite, Calculus.\r\n\r\nTherefore, those who were awaiting Ruport 0.2.0 with it\'s wonderful bug fixes or Ruport 0.3.0 with Charting rudiments and PDF::Writer / Parse-input integration, I would not hold your breath just yet.\r\n\r\nAnd for those of you expecting the Stone Code Web Application, sorry but it\'ll be a while.\r\n\r\nI simply need to get on track with school, an unfortunate but necessary task. Don\'t be sad though, this is all in preparation for a cloak and daggar move which cannot yet be announced regarding Stone Code\'s future, though I assure you... it will make this wait well worth it.\r\n\r\nSo if my development crawls in the coming weeks, many apologies. You might see a decent sprint on thanksgiving, but no promises just yet.\r\nAnd trust me, winter break brings surprises to be had!','Long Story Short, Short Story Long',0,'','publish','open','open','','long-story-short-short-story-long','','','2005-10-20 01:33:09','2005-10-20 05:33:09','',0,'http://www.stonecode.org/blog/?p=29',0),(30,2,'2005-10-25 04:16:55','2005-10-25 08:16:55','So I sort of lied about shying away from the lady in red for a short time.\r\n\r\nI couldn\'t resist coding up a little bit of voodoo to help me do my C homework.\r\n\r\nPart watered down rake, part simple markup language, part insanity, I introduce Foo 0.0.0\r\n\r\nIt\'s in the public domain, it has more dependencies than a crackhead... and here is it\'s list of commands:\r\n\r\ncompile,run,output,$, !, __, **, ~\r\n\r\nHmm... the end there really goes into Perl-la land doesn\'t it?\r\n\r\nSo here you are Ethan... but don\'t say I didn\'t warn ya\'s!\r\n\r\nOh yeah, why Public Domain? Because I was in the mood to literally throw this sucker away, publicly! :)\r\n\r\n\r\n ','Foo 0.0.0',0,'','publish','open','open','','foo-000','','','2005-10-25 04:18:13','2005-10-25 08:18:13','',0,'http://www.stonecode.org/blog/?p=30',0),(31,2,'2005-10-26 03:03:34','2005-10-26 07:03:34','Anyone who is able to commute to NYC once a month and does not attend NYC.rb meetings is totally out of their mind!\r\n\r\nRob and I took a trip down to Matt Pelletier\'s office to find around 20 Rubyists circling around a projector. Lo and behold, Francis Hwang and pals were providing a RubyConf 2005 replay for their group! It was interesting in a way for those of us who attended to go through and share our experiences as well as to hear the insight of those who could not attend. It was far less formal and the open discussions were quite interesting.\r\n\r\nThe discussions ranged from business practices to design methodology to specific technologies and their usage. The chemistry of the room was similar to that of the RubyConf, for which I was absolutely thrilled. \r\n\r\nAfter going through a few of the presentations, we collectively tried to wrap our heads around continuations, and failed. Nevertheless, we did gain a bit of a feel for their magic and that was exciting. \r\n\r\nPizza + Beer + NYC.rb = One hell of an awesome night.\r\n\r\nThe meetings for NYC.rb are the last Tuesday of every month, though Francis may be increasing them to be twice a month.\r\n\r\nFor those who are members of UNH.rb (Soon to be NewHaven.rb), you are welcome to tag along with Rob and I for an excellent experience.\r\n\r\nThanks a ton NYC.rb, we had a killer time!\r\n','NYC.rb',0,'','publish','open','open','','nycrb','','','2005-10-26 03:04:24','2005-10-26 07:04:24','',0,'http://www.stonecode.org/blog/?p=31',0),(32,2,'2005-10-27 03:39:05','2005-10-27 07:39:05','Been busy with this and that.','Oh, the places you\'ll go!',0,'','publish','open','open','','oh-the-places-youll-go','','','2005-10-27 03:39:05','2005-10-27 07:39:05','',0,'http://www.stonecode.org/blog/?p=32',0),(33,2,'2005-10-29 16:04:41','2005-10-29 20:04:41','(update: Aslak points out these are Fake objects rather than mocks. Either they will be replaced with true mocks or nomenclature will be changed in Ruport 0.3.0)\r\n\r\nRuport now has MockQuery and MockDB so it no longer will catch your hair on fire trying to get the unit tests to work. These are not super duper robust, but they do the trick to test Ruport functionality. I will soon be introducing fixtures of some sort so that functional testing is possible, but for now, the unit tests are much easier to get running (should just work by \'rake\' with no config).','Stop (STUBBING) Me!',0,'','publish','open','open','','stop-mocking-me','','','2005-11-09 11:11:12','2005-11-09 15:11:12','',0,'http://www.stonecode.org/blog/?p=33',0),(34,2,'2005-11-09 02:46:52','2005-11-09 06:46:52','So i\'ve been super busy which would explain the lack of bloggage.\r\n\r\nRuport 0.2.0 came out at the crack of dawn on November 8th. It was about a month behind schedule, but features a treasure trove of new features which were never slated to go in.\r\n\r\nnew_haven.rb officially formed. We had our first get together last friday, and there were a total of 8 of us for dinner. We began talking about a potential group project (an intelligent bug tracking / knowledgebase system)\r\n\r\nOur first meeting is tomorrow from 7pm to 9pm and I\'m doing a talk on unit testing. Wheee :)\r\n\r\nMore chatter soon!','Ruport 0.2.0 is out, new_haven.rb formed, and I\'ve waited a long time to update',0,'','publish','open','open','','ruport-020-is-out-new_havenrb-formed-and-ive-waited-a-long-time-to-update','','','2005-11-09 02:46:52','2005-11-09 06:46:52','',0,'http://www.stonecode.org/blog/?p=34',0),(35,2,'2005-11-10 02:14:33','2005-11-10 06:14:33','So tonight was our first meeting. And to my surprise, i walked into the place to see David Black just sitting there.\r\n\r\nIt was amazing to see someone from RubyCentral attending a meeting at a group I thought would never get off the ground.\r\n\r\nI guess I was mistaken. 13 strong, this group of ruby newbies and intermediates alike came together with some incredible chemistry.\r\n\r\nIt was amazing to see all these people getting together, talking about many great ideas, sharing some of the problems they\'ve been having, etc.\r\n\r\nIt was also scary as hell to present a talk on unit tests to a group that I expected to be 3 or 4 strong, and newbies at that.\r\n\r\nThe main chunks of the meeting were my talk, Paul\'s talk on Solitaire Cypher, a code review of one member\'s Rails project, and an open discussion featuring a small argument regarding type safety and also whether or not \'walks like a duck\' is sufficient or if we need a more solid indicator for the users of libraries. (I was on the \'you don\'t need it\' side of the fence but Gary and others made some excellent points)\r\n\r\nThe entire Stone code team was there, Rob, Greg G., and myself. David Black found a bug in one of Ruport\'s test cases that I thought i fixed before the release, but obviously didn\'t. The good news is I switched the suspicious assert() call to the intended assert_equal() and Ruport still passed.\r\n\r\nI am very unpleased with how DataSet#eql? is working (or not working) and I also don\'t like the test file ruport is generating when you call ruport_generate. I\'d also like to automate SQL build scripts if possible. It seems like a Ruport 0.2.1 is in order for this weekend, so get ready to run gem update for some minor cleanup by the end of this weekend. \r\n\r\nI am totally psyched about new_haven.rb, the group is amazing. Thanks to all who attended and participated.\r\n\r\n','new_haven.rb first meeting / ruport bugs',0,'','publish','open','open','','new_havenrb-first-meeting-ruport-bugs','','','2005-11-10 02:17:04','2005-11-10 06:17:04','',0,'http://www.stonecode.org/blog/?p=35',0),(36,2,'2005-11-11 09:35:47','2005-11-11 13:35:47','Well I woke up at 7am with the sun blaring in my eyes and decided it was time to get crackin\' on Ruby Report 0.2.2\r\n\r\nRight now I\'m looking at a totally refactored organization. \r\n\r\nRuport::Report::Engine, \r\nRuport::Report::Mailer, etc\r\n\r\nif you call require \'ruportlib\'\r\n\r\nthen it\'s just Report::Mailer, Report::DataSet, etc\r\n\r\nIt seems to make more sense. Report#generate_report ?\r\n\r\nSounds better than Query#generate_report but I think Report::Engine#generate_report sounds better.\r\n\r\nAnyway, gotta figure that out. I wish James wasn\'t on vacation so he could help me with this stuff.\r\n\r\nThe point was that I\'ve totally reworked the structure and I\'m back to passing all tests in roughly an hour.\r\n\r\nTry to do that in Java, foo!','Ruport Oh Deuce Duece, rockin and rollin\'',0,'','publish','open','open','','ruport-oh-deuce-duece-rockin-and-rollin','','','2005-11-11 09:38:01','2005-11-11 13:38:01','',0,'http://www.stonecode.org/blog/?p=36',0),(37,2,'2005-11-11 12:17:54','2005-11-11 16:17:54','We\'re at 0.2.2 now.\r\n\r\nGood biddles all around :)\r\n\r\nhttp://ruport.rubyforge.org','Ruport 0.2.2 Released',0,'','publish','open','open','','ruport-022-released','','','2005-11-11 12:18:43','2005-11-11 16:18:43','',0,'http://www.stonecode.org/blog/?p=37',0),(38,2,'2005-11-12 13:42:47','2005-11-12 17:42:47','To me, agile development is nothing more than coding by exception, applied to behaviors. You try something, you get some feedback, you change a few things, you get more feedback. It\'s exactly the same as running a piece of code and it dumping an error like \"You\'ve got a foo where there should be a bar on line 12\". Of course, life is more subtle than this, and we\'re often thrown the \"Syntax error on line 80000\" when you\'re dealing with a 20 line piece of code. These type of pitfalls are where we simply need to use deduction to find the source of the problem, and not rely on the flawed feedback we\'re getting. As agile developers, we understand this practice well when it comes to programming. Sometimes, the feedback you get is not the right feedback you need to get the job done. So it follows that we should practice this in our behaviors as well. \r\n\r\nWhen I first started out programming, I was very good at building things exactly as a customer told me to. But this is not Agile nor is it a good business practice. My first few customers were quite displeased, no matter how close I came to meeting there expectations. To me, this is like typing perfectly valid code that does the complete wrong job. You say... \"there was no error when it started up\" and then when you click the big red button, your monitor shuts off instead of an email being sent or whatever it was supposed to do. These are the more subtle errors that we need to check by inspection and use a bit of intuition to solve. This is why understanding what the customer wants is more valuable than understanding what they say. The more technically adept, the more dangerous a customer becomes. My current employer is a skilled DBA. He is not a skilled rubyist. He\'s a smart guy so he\'s pretty good at explaining what he wants, but then when it comes down to how he thinks it should be done, it\'s really important to refactor his plans to make more sense for the real problem at hand. I am finally learning (through trial by fire), that an open and insightful employer WILL let you make design decisions that differ from theirs as long as it makes sense within the problem domain. So to me... the informal specifications are kind of like your logical unit tests, so it\'s important to be sure you\'re testing for the right conditions. If you forget the edge cases, throw an assert where there should be an assert_equal, or just frame your tests so that they meet the wrong conditions, you\'re working against yourself.\r\n\r\nSo, my only point in this rant is that when you think about Agile Software Development, go ahead and read The Pragmatic Programmer and dig into the core of the Agile Manifesto and all of that stuff. It\'s really a motivating experience and there is a ton of practical knowledge to be gained there. But when it boils down to applying it, don\'t try terribly hard to fit some pattern, don\'t worry that Dave Thomas or Martin Fowler is going to pop out of the closet and say \"That\'s not agile!\". Just sit down, and think about how you do your work. What comes naturally when it\'s just you and the compiler / interpreter and a ream of source code. Now think of applying that to your behaviors without alienating your customer. Grab a piece of paper, draw some mock ups. Write some unit tests during a meeting, forget the UML that they don\'t understand and you can\'t use. Do a quick one up prototype, get some people around the monitor and let them say \"Foo is cool but I hate bar\". \r\n\r\nAgile has become this insane buzzword and people are acting like it\'s this way of life. Well, it is a way of life, but you already know about it well. It\'s just a matter of applying it beyond your interaction with the keyboard and into the meeting room or the coffee shop, or wherever your business needs doing.\r\n\r\n','Agile works because it\'s what developers know best...',0,'','publish','open','open','','agile-development-works-because-its-what-developers-know-best','','','2005-11-12 13:45:53','2005-11-12 17:45:53','',0,'http://www.stonecode.org/blog/?p=38',0),(39,2,'2005-11-29 00:09:31','2005-11-29 04:09:31','Ruport 0.2.9 \'I didn\'t sleep for 2 days straight\' Edition is out:\r\n\r\nOriginal Announcement Here\r\n\r\nI would write more but I am seriously tired...\r\n','Ruport 0.2.9',0,'','publish','open','open','','ruport-029','','','2005-11-29 00:09:31','2005-11-29 04:09:31','',0,'http://www.stonecode.org/blog/?p=39',0),(40,2,'2005-12-03 04:50:50','2005-12-03 08:50:50','One of the highest search references this site has been hit with this month have to do with Ruport Rails integration...\r\n\r\nWell... will Ruport ever directly support Rails? No.\r\n\r\nHere are three pieces of good news though:\r\n\r\n1) I am putting ActiveRecord support in within the next couple weeks.\r\n (Along with Lafcadio and Og and possibly KirbyBase, hopefully)\r\n\r\n2) I am building a commercial free software application that will be using Ruport on Rails. There will be free (as in beer and speech) community versions of this software available eventually, within the next year or two, but the subversion will be available within the next month to serious developers and people who have special needs to see or use the code and whatnot. I\'m actually looking for help with this project, which would involve meager pay and a nearly non-existant workplace in New York City in early January, so email me if you\'re interested in this.\r\n\r\n3) I am proof of concepting one of hopefully many components I\'ll be building for Ruport on Rails (here known as The Helios Project) and will post a link on my blog to where you can download it. This should show up some time later this week.\r\n','Ruport on Rails???',0,'','publish','open','open','','ruport-on-rails','','','2005-12-03 04:52:39','2005-12-03 08:52:39','',0,'http://www.stonecode.org/blog/?p=40',0),(41,2,'2005-12-05 15:58:51','2005-12-05 19:58:51','I totally forgot (until yesterday) that I made a little MineSweeper game while waiting for my plane on the way back from Oklahoma.\r\n\r\nSo today, I committed it to the CVS and you can see the massive source code for the application (60 lines) here\r\n\r\n
    UPDATE: Some recursion was added that \'almost\' works when auto filling zero mines, the code is about 75 lines now.
    \r\n\r\nDue to the gambit codeless server, getting this to run was as simple as:\r\nruby game.rb -p 3333 -d\r\n\r\nAnd yeah, I ran it on this very server, so go ahead and create a player and a game and see what a half hour of coding 6 months ago did. :)\r\n\r\nMineSweeper server\r\n\r\n\r\n\r\n\r\n\r\n\r\n','MineSweeper?!?',0,'','publish','open','open','','minesweeper','','','2005-12-05 17:19:25','2005-12-05 21:19:25','',0,'http://www.stonecode.org/blog/?p=41',0),(42,2,'2005-12-06 11:57:23','2005-12-06 15:57:23','http://stonecode.org:1999/\r\n\r\nCreate a player (You need to create new players for each game, and they might break your other games if you don\'t use the same name. Annoying, I know)\r\n\r\nThen hop into the Main Gambit Mailserver\r\n\r\nhit compose.\r\n\r\nSend lovely messages to other gambiters.','Behold... G(ambit)Mail!',0,'','publish','open','open','','behold-gambitmail','','','2005-12-06 11:58:16','2005-12-06 15:58:16','',0,'http://www.stonecode.org/blog/?p=42',0),(43,2,'2005-12-08 20:00:30','2005-12-09 00:00:30','So.. when you add up the stats from the gem server (Now 374, was 263 at time of release) and the stats on the download page for Ruport 0.2.9, it exceeds 220! That is really overwhelming considering the fact that it\'s been less than 2 weeks since the release.\r\n\r\nStill, the feedback is kind of quiet as far as bug fixes and problems for 0.3.0 go.\r\ni KNOW theres gots to be bugs out there (In fact, I fixed one in ODBC the other day ;) )\r\n\r\nSo what are your problems folks? Oh... wait... that wasn\'t meant to sound combative!\r\n\r\nWhat problems are you having with Ruport... ah yes.. that\'s less confrontational\r\n\r\nOh yeah, i did a presentation on Ruport today. You can grab a PDF of it, my lecture notes, and some handy dandy example stuff (including ruport_demo.rb, which makes IRb a playground for Ruport :) ) if you want.\r\n\r\nIt\'s here: http://stonecode.org/ruport/ruport.zip','220 downloads can\'t be wrong.',0,'','publish','open','open','','220-downloads-cant-be-wrong','','','2005-12-08 20:02:20','2005-12-09 00:02:20','',0,'http://www.stonecode.org/blog/?p=43',0),(45,2,'2005-12-13 11:18:40','2005-12-13 15:18:40','So, I added myself over at Advogato, finally.\r\n\r\nhttp://advogato.org/person/GregoryBrown/\r\n\r\nIn other interesting news, take a look at this excerpt from the CHANGELOG of Ruport:\r\n
    \r\n- Fixed a bug in query that made ODBC driver not work at ALL!\r\n (AFAIK, this bug was ONLY in Ruport 0.2.9)\r\n\r\n- removed render() from the engine and implemented DataSet#render_as()\r\n example:\r\n @report = render(data) do |builder| builder.format = :some_format end\r\n is now: @report = data.render_as(:some_format)\r\n which takes an optional block that works as before.\r\n\r\n- removed method DataSet#select_field() because it was the same as \r\n DataSet#select_fields() and of limited utility.\r\n\r\n- added method DataSet#remove_fields and DataSet#remove_fields!\r\n to make data manipulation easier.\r\n\r\n- added DataSet#empty?\r\n\r\n- added DataSet#clone which will actually deep copy a DataSet.\r\n\r\n- cleaned up incredibly annoying DataSet constructor so you can now pass\r\n field names and data to new()\r\n\r\n- Report::DataSet now documented\r\n\r\n- added hacks.rb which will include a random collection of potentially\r\n useful functions. (Already cool for making Ruport easy to use with irb).
    \r\n\r\nYep. Whether or not it\'s finals week, 0.3.0 is getting close to release :)\r\nI just need more DOCUMENTATION, ugh...','Shameless Self Promotion and Ruport News',0,'','publish','open','open','','shameless-self-promotion-and-ruport-news','','','2005-12-13 11:19:57','2005-12-13 15:19:57','',0,'http://www.stonecode.org/blog/?p=45',0),(46,2,'2005-12-21 02:06:39','2005-12-21 06:06:39','The bad news is there will be no Ruport 0.3.0\r\n\r\nThe good news is that I have successfully moved to NYC for the winter\r\nbreak and have already begun the process of dedicating the next couple\r\nweeks exclusively to Ruport.\r\n\r\nRuby Reports 0.5.0 is truely going to be a whole new piece of\r\nsoftware, and I\'m very excited about that. We\'re going to have a\r\nprintable document system, which will feature both an abstract layer\r\nand a direct implementation via PDF... we\'re going to have support for\r\nthe popular ORMs, all the features I claimed would be in 0.3.0, and\r\nmore!\r\n\r\nFor those who want to take an active role in the development of Ruport\r\nin the coming weeks, I\'ve created a basecamp account. Of course,\r\nafter this sprint is completed, I\'ll announce the release to everyone\r\non the list showing off all the glittering features and whatnot, but\r\nuntil then, if you think you\'ll actively contribute or test the code\r\nor do something else useful like that, email me and I\'ll add you to\r\nthe basecamp project.\r\n\r\nOtherwise, see you all in a couple weeks!','The good and the bad',0,'','publish','open','open','','the-good-and-the-bad','','','2005-12-27 05:53:09','2005-12-27 09:53:09','',0,'http://www.stonecode.org/blog/?p=46',0),(47,2,'2005-12-25 22:47:03','2005-12-26 02:47:03','My laptop decided to die today. This is especially painful news considering my desktop computer is locked away at the University of New Haven and I was planning on using my laptop as my primary rig during the Ruport code fest in the coming week, and the helios codefest thereafter.\r\n\r\nI have found an interim solution (Read: Windows machine) for the short term, but it looks like the $300 I had set aside for donations to a handful of organizations with the word Ruby in them along with a random humanitarian charity will have to wait a couple weeks now until I figure out what to do about the laptop situation. :(\r\n\r\nAt least my FSF associate membership and $100 donation to Gentoo went through before this catastrophe. That having been said, my next machine will NOT be a mac.\r\n\r\nSo... my development efforts will not be thwarted... however, my spirits are a bit dimmed by the sadness of this whole situation :-/','Some sad news',0,'','publish','open','open','','some-sad-news','','','2005-12-25 22:47:03','2005-12-26 02:47:03','',0,'http://www.stonecode.org/blog/?p=47',0),(48,2,'2005-12-26 20:49:49','2005-12-27 00:49:49','Yes. I am on drugs.\r\n\r\nThis will potentially be done by New Years Day. No, I\'m not joking.\r\n\r\nRuport 0.5.0 specification\r\n\r\nIf you want to stay abreast of the development in the coming week, you\'ll need to make your way to the mailing list.\r\n','Ruport 0.5.0 Specification',0,'','publish','open','open','','ruport-050-specification','','','2005-12-27 05:52:38','2005-12-27 09:52:38','',0,'http://www.stonecode.org/blog/?p=48',0),(49,2,'2005-12-27 05:51:25','2005-12-27 09:51:25','Well, thanks to the kindness of Gregory Gibson for purchasing a company laptop a while ago and to the kindness of Robert Canieso for lending me said laptop, Ruport\'s development will not be halted by Windows hell in the coming week.\r\n\r\nI am on a shiny new distribution of Ubuntu Linux, and this is already even better than having to deal with my 95% level of satisfaction with OS X. Though Ubuntu would probably be the 98% to Gentoo\'s 100%, it\'s enough to keep me very comfortable for the coming week.\r\n\r\nI\'m basically behind a day because of the perils of setting up a new rig, but I will be working overtime until we get back on track. So, for those of you who love screenshots, here\'s one','Up and running.',0,'','publish','open','open','','up-and-running','','','2005-12-27 05:51:25','2005-12-27 09:51:25','',0,'http://www.stonecode.org/blog/?p=49',0),(50,2,'2005-12-28 14:03:00','2005-12-28 18:03:00','\r\n`ls`.grep(\' \',\'?\').split.sort_by { rand }.each do |song|\r\n  system \"mplayer #{song}\"\r\nend\r\n','Who needs a fancy media player?',0,'','publish','open','open','','who-needs-a-fancy-media-player','','','2005-12-28 14:04:29','2005-12-28 18:04:29','',0,'http://www.stonecode.org/blog/?p=50',0),(51,2,'2005-12-31 12:21:06','2005-12-31 16:21:06','Okay, so if you want to lay out a structure for a document before you use it via ERb or a Ruport script or from within Format::Builder, you now can.\r\n\r\nYou\'ll be able to do your layout in pure ruby, XML, or YAML. Support for pure ruby was implemented last night. And while sitting here in grand central terminal, I just finished Document::parse_xml :)\r\n\r\nYAML support should be quite easy to add, so expect it added in today as well. The cool thing is that you\'ll be able to convert between the formats, so if you want to steal someone\'s structure script that was written in XML, you can load it in and dump it out as YAML, or if you need to communicate with some XML process, you can easily do the reverse, too.\r\n\r\nCool?','Ruport\'s Document class now handles XML',0,'','publish','open','open','','ruports-document-class-now-handles-xml','','','2005-12-31 12:23:00','2005-12-31 16:23:00','',0,'http://www.stonecode.org/blog/?p=51',0); +UNLOCK TABLES; +/*!40000 ALTER TABLE `wp_posts` ENABLE KEYS */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/test/samples/test.sql b/test/samples/test.sql new file mode 100644 index 00000000..473b8cac --- /dev/null +++ b/test/samples/test.sql @@ -0,0 +1,2 @@ +SELECT * FROM ruport_test + diff --git a/test/samples/test.yaml b/test/samples/test.yaml new file mode 100644 index 00000000..fe27f20b --- /dev/null +++ b/test/samples/test.yaml @@ -0,0 +1,3 @@ +:name: Greg +:friend: Rob +:job: Programmer diff --git a/test/tc_config.rb b/test/tc_config.rb new file mode 100644 index 00000000..317297cd --- /dev/null +++ b/test/tc_config.rb @@ -0,0 +1,89 @@ +require "test/unit" +require "ruport" + +class TestConfiguration < Test::Unit::TestCase + + def setup + Ruport::Config.init! + Ruport::Config.log_file "test/unit.log" + end + + def test_dsn_defaults + assert_equal(nil, Ruport::Config.default_source) + end + + def test_mail_defaults + assert_equal(nil, Ruport::Config.default_mailer) + end + + def test_init + Ruport::Config.init! + assert_equal({}, Ruport::Config.sources) + assert_equal({}, Ruport::Config.mailers) + assert_equal(nil,Ruport::Config.logger) + assert_equal(false,Ruport::Config.paranoid?) + end + + def test_missing_dsn + assert_raise(ArgumentError) { + Ruport::Config.source :foo, :user => "root", :password => "fff" + } + assert_nothing_raised { Ruport::Config.source :bar, :dsn => "..." } + end + + def test_mailer_errors + assert_raise(ArgumentError) { + Ruport::Config.mailer :bar, :user => :foo, :address => "foo@bar.com" + } + assert_nothing_raised { Ruport::Config.mailer :bar, :host => "localhost" } + end + + def test_new_defaults + Ruport::Config.source :default, :dsn => "dbi:mysql:test", + :user => "root", + :password => "" + assert_equal("dbi:mysql:test", Ruport::Config.default_source.dsn) + assert_equal("root", Ruport::Config.default_source.user) + assert_equal("", Ruport::Config.default_source.password) + end + + def test_multiple_sources + Ruport::Config.source :foo, :dsn => "dbi:mysql:test" + Ruport::Config.source :bar, :dsn => "dbi:mysql:test2" + assert_equal("dbi:mysql:test", Ruport::Config.sources[:foo].dsn) + assert_equal("dbi:mysql:test2", Ruport::Config.sources[:bar].dsn) + end + + def test_simple_interface + Ruport.configure do |c| + c.source :foo, :dsn => "dbi:odbc:test" + c.source :bar, :dsn => "dbi:odbc:test2" + end + assert_equal("dbi:odbc:test",Ruport::Config.sources[:foo].dsn) + assert_equal("dbi:odbc:test2",Ruport::Config.sources[:bar].dsn) + end + + + def test_logger + # We have a logger running now, dont we? + assert(Ruport::Config.logger.kind_of?(Logger)) + + # If we could go back in time and never define one... + Ruport::Config.init! + assert Ruport::Config.logger.nil? + + # And then we change are mind again. Back logging? + Ruport::Config.log_file "test/unit.log" + assert(Ruport::Config.logger.kind_of?(Logger)) + + end + + def test_paranoia + assert_equal(false, Ruport::Config.paranoid?) + Ruport::Config.enable_paranoia + assert_equal(true, Ruport::Config.paranoid?) + Ruport::Config.disable_paranoia + assert_equal(false, Ruport::Config.paranoid?) + end + +end diff --git a/test/tc_data_row.rb b/test/tc_data_row.rb new file mode 100644 index 00000000..1e45f87b --- /dev/null +++ b/test/tc_data_row.rb @@ -0,0 +1,124 @@ +#!/usr/local/bin/ruby -w + +require "test/unit" +require "ruport" + +class TestDataRow < Test::Unit::TestCase + + include Ruport + + def setup + @rows = DataSet.new + @rows.fields = %w[ foo bar ] + @rows << [ 1 , 2 ] + @rows << [ 3 , 4 ] + @rows << [ 5 , 6 ] + @rows << { "foo" => 7, "bar" => 8 } + @rows << [ 9, 10 ] + end + + def test_to_s + assert_equal("[1,2]",@rows[0].to_s) + end + + def test_tagging + @rows[0].tag_as :foo + assert_equal(true, @rows[0].has_tag?(:foo) ) + assert_equal( false,@rows[1].has_tag?(:foo) ) + assert_equal(false,@rows[0].has_tag?(:bar) ) + assert_equal({:foo => true},@rows[0].tags) + @rows[0].tag_as :apple + assert_equal({:foo => true, :apple => true},@rows[0].tags) + assert_equal({},@rows[2].tags) + end + + def test_constructor + row = Ruport::DataRow.new(%w[a b c], :data => [1,2,3]) + row2 = Ruport::DataRow.new(%w[a b c], :data => {"a" => 1, "b" => 2, "c" => 3}) + row3 = Ruport::DataRow.new(%w[a b c], :data => row2) + row4 = Ruport::DataRow.new(%w[a b c], {:data => row3, :tags => [:awesome, :cool]}) + + assert_equal(row, row2) + assert_equal(row2, row3) + + assert_equal(%w[a b c], row.fields) + assert_equal(1, row[0]) + assert_equal(1, row["a"]) + assert_equal(2, row[1]) + assert_equal(2, row["b"]) + assert_equal(3, row[2]) + assert_equal(3, row["c"]) + assert_equal([:awesome,:cool], row4.tags) + + end + + def test_brackets + r1 = @rows[0] + r2 = @rows[3] + + assert_equal( 1, r1[0] ) + assert_equal( 2, r1[1] ) + assert_equal( 1, r1["foo"] ) + assert_equal( 2, r1["bar"] ) + + assert_equal( 7, r2[0] ) + assert_equal( 8, r2["bar"] ) + assert_equal( 8, r2[:bar] ) + + r1[1] = "apple" + r1[:foo] = "banana" + + assert_equal( r1["foo"], "banana" ) + assert_equal( r1[:bar], "apple" ) + + assert_nothing_raised { r1[:apples] } + assert_equal( r1[:apples], nil ) + end + + def test_equality + assert( Ruport::DataRow.new(%w[ a b ], :data => [1,2]) == + Ruport::DataRow.new(%w[ a b ], :data => [1,2]) ) + assert( Ruport::DataRow.new(%w[ a b ], :data => [1,2]) != + Ruport::DataRow.new(%w[ c d ], :data => [1,2]) ) + a = Ruport::DataRow.new(%w[ a b c ], :data => [1,2,3]) + a.tag_as :apple + b = Ruport::DataRow.new(%w[ a b c ], :data => [1,2,3]) + assert( a == b ) + assert( a.eql?(b) ) + + end + + def test_addition + row1 = Ruport::DataRow.new %w[ a b c ], :data => [1,2,3] + row2 = Ruport::DataRow.new %w[ d e f ], :data => [4,5,6] + + expected = Ruport::DataRow.new %w[ a b c d e f ], :data => [1,2,3,4,5,6] + + assert_equal( expected, row1 + row2 ) + end + + def test_clone + row1 = Ruport::DataRow.new %w[ a b c ], :data => [1,2,3] + row2 = row1.clone + + assert( row1.object_id != row2.object_id ) + row1.tag_as :original + assert( row1.has_tag?(:original) ) + assert( ! row2.has_tag?(:original) ) + end + + #FIXME: add edge cases + def test_to_h + row1 = Ruport::DataRow.new %w[ a b c ], :data => [1,2,3] + assert_instance_of(Hash,row1.to_h) + assert_equal({ "a" => 1, "b" => 2, "c" => 3},row1.to_h) + end + + def test_ensure_arrays_not_modified + arr = [1,2,3] + row1 = Ruport::DataRow.new %w[ a b c ], :data => arr + assert_equal( [1,2,3], arr ) + end + +end + diff --git a/test/tc_data_set.rb b/test/tc_data_set.rb new file mode 100644 index 00000000..e82b5f89 --- /dev/null +++ b/test/tc_data_set.rb @@ -0,0 +1,367 @@ +#!/usr/local/bin/ruby -w + +require "test/unit" +require "ruport" + +class TestDataSet < Test::Unit::TestCase + + include Ruport + + def setup + @data = DataSet.new + @data.fields = %w[ col1 col2 col3 ] + @data.default = "" + @data << %w[ a b c ] << { "col1" => "d", "col3" => "e"} + end + + def test_new + fields = %w[ col1 col2 col3 ] + my_data = DataSet.new(fields) + assert_equal(fields,my_data.fields) + + my_filled_data = DataSet.new( fields, :data => [[1,2,3],[4,5,6]] ) + + assert_equal( [[1,2,3],[4,5,6]], my_filled_data.map { |r| r.to_a } ) + + hash_filling = [ { "col1" => 9, "col2" => 6, "col3" => 0 }, + { "col1" => 54, "col3" => 1 } ] + + my_filled_data = DataSet.new(fields, :data => hash_filling) + + assert_equal( [[9,6,0],[54,nil,1]], my_filled_data.map { |r| r.to_a } ) + + cloned_set = @data.clone + + assert_equal( [ %w[a b c], ["d","","e"] ], cloned_set.map { |r| r.to_a } ) + end + + def test_fields + assert_equal(%w[ col1 col2 col3 ], @data.fields ) + end + + def test_default + @data.default = "x" + @data << { } + assert_equal( ['x','x','x'], + @data[2].to_a ) + end + + def test_delete_if + @data.delete_if { |r| r.any? { |e| e.empty? } } + assert_equal([%w[a b c]],@data.to_a) + end + + def test_brackets + row0 = { "col1" => "a", "col2" => "b", "col3" => "c" } + row1 = { "col1" => "d", "col2" => "", "col3" => "e" } + row0.each do |key,value| + assert_equal( value, @data[0][key] ) + end + row1.each do |key,value| + assert_equal( value, @data[1][key] ) + end + end + + def test_eql? + data2 = DataSet.new + data2.fields = %w[ col1 col2 col3 ] + data2.default = "" + data2 << %w[ a b c ] + data2 << { "col1" => "d", "col3" => "e" } + + #FIXME: This looks like some shady discrete math assignment + assert(@data.eql?(data2) && data2.eql?(@data)) + data2 << [2, 3, 4] + assert(!( @data.eql?(data2) || data2.eql?(@data) )) + @data << [2, 3, 4] + assert(@data.eql?(data2) && data2.eql?(@data)) + @data << [8, 9, 10] + assert(!( @data.eql?(data2) || data2.eql?(@data) )) + data2 << [8, 9, 10] + assert(@data.eql?(data2) && data2.eql?(@data)) + end + + def test_shaped_like? + a = DataSet.new + a.fields = %w[ col1 col2 col3 ] + assert(@data.shaped_like?(a)) + assert(a.shaped_like?(@data)) + end + + def test_union + a = DataSet.new + a.fields = %w[ col1 col2 col3 ] + a << %w[ a b c ] + a << %w[ x y z ] + b = a | @data + assert_kind_of(DataSet, b) + assert_equal(b.data.length, 3) + assert_equal([ %w[a b c], %w[x y z], ["d","","e"] ], b.to_a) + assert_equal((a | @data), a.union(@data)) + end + + def test_difference + a = DataSet.new + a.fields = %w[ col1 col2 col3 ] + a << %w[ a b c ] + a << %w[ x y z ] + b = a - @data + assert_kind_of(DataSet, b) + assert_equal(b.data.length, 1) + assert_equal([ %w[x y z] ], b.to_a) + assert_equal((a - @data), a.difference(@data)) + end + + def test_intersection + a = DataSet.new + a.fields = %w[ col1 col2 col3 ] + a << %w[ a b c ] + a << %w[ x y z ] + b = a & @data + assert_kind_of(DataSet, b) + assert_equal(b.data.length, 1) + assert_equal([ %w[a b c] ], b.to_a) + assert_equal((a & @data), a.intersection(@data)) + end + + def test_concatenation + a = DataSet.new + a.fields = %w[ col1 col2 col3 ] + a << %w[ x y z ] + newdata = @data.concat(a) + assert_equal([ %w[a b c], ["d","","e"], %w[x y z] ], newdata.to_a) + end + + def test_append_datarow + row = DataRow.new(%w[ x y z ], :data => %w[ col1 col2 col3 ]) + @data << row + assert_equal(@data[2], row) + end + + def test_append_datarows + row = DataRow.new(%w[ x y z ], :data => %w[ col1 col2 col3 ]) + row2 = DataRow.new(%w[ u v w ], :data => %w[ col1, col2, col3 ]) + @data << [row, row2] + assert_equal(@data[2], row) + assert_equal(@data[3], row2) + end + + def test_empty? + a = DataSet.new + a.fields = %w[a b c] + assert_equal(true,a.empty?) + assert_equal(false,@data.empty?) + + a << [1,2,3] + assert_equal(false,a.empty?) + end + + def test_length + assert_equal(2, @data.length) + @data << [1,2,3] + assert_equal(3, @data.length) + end + + def test_load + loaded_data = DataSet.load("test/samples/data.csv", :default => "") + assert_equal(@data,loaded_data) + + loaded_data = DataSet.load("test/samples/data.csv", :has_names => false) + + assert_equal(nil,loaded_data.fields) + assert_equal([%w[col1 col2 col3],%w[a b c],["d",nil,"e"]],loaded_data.to_a) + assert_equal(%w[a b c],loaded_data[1].to_a) + + + loaded_data = DataSet.load("test/samples/data.csv") do |set,row| + set << row if row.include? 'b' + end + assert_equal([%w[a b c]],loaded_data.to_a) + end + + def test_to_csv + loaded_data = DataSet.load("test/samples/data.csv" ) + csv = loaded_data.to_csv + assert_equal("col1,col2,col3\na,b,c\nd,,e\n",csv) + end + + def test_to_html + assert_equal("\n\t\t\n\t\t\t\n\t\t\t"+ + "\n\t\t\t\n\t\t\n"+ + "\t\t\n\t\t\t\n\t\t\t\n\t\t\t"+ + "\n\t\t\n\t\t\n\t\t\t\n\t"+ + "\t\t\n\t\t\t\n\t\t"+ + "\n\t
    col1 col2 col3
    abc
    d e
    ", @data.to_html ) + end + + def test_select_columns + + b = @data.select_columns(0,2) + assert_equal(%w[col1 col3], b.fields) + assert_equal([%w[a c],%w[d e]],b.to_a) + + assert_equal( [["a"],["d"]], + @data.select_columns("col1").to_a ) + assert_equal( [["b"],[""]] , + @data.select_columns("col2").to_a ) + assert_equal( [["c"],["e"]], + @data.select_columns("col3").to_a ) + assert_equal( [["a","b","c"],["d","","e"]], + @data.select_columns("col1","col2","col3").to_a ) + assert_equal( [["c","a"],["e","d"]], + @data.select_columns("col3","col1").to_a ) + + end + + def test_select_columns! + a = [[1,2],[3,4]].to_ds(%w[a b]) + a.select_columns!(*%w[b a]) + + assert_equal(%w[b a],a.fields) + assert_equal([[2,1],[4,3]],a.to_a) + + a.select_columns!('a') + + assert_equal(%w[a], a.fields) + assert_equal([[1],[3]],a.to_a) + + a.select_columns!('a','q') + assert_equal(%w[a q], a.fields) + assert_equal([[1,nil],[3,nil]],a.to_a) + + a[0]['q'] =2 + assert_equal([[1,2],[3,nil]],a.to_a) + + end + + def test_as + data = DataSet.new + data.fields = %w[ col1 col2 col3] + data << %w[ a b c] + data << %w[ d e f] + assert_equal("col1,col2,col3\na,b,c\nd,e,f\n", + data.as(:csv) ) + + assert_equal("\n\t\t\n\t\t\t"+ + "\n\t\t\t\n\t\t\t"+ + "\n\t\t\n\t\t\n\t\t\t\n\t\t\t"+ + "\n\t\t\t\n\t\t\n\t\t"+ + "\n\t\t\t\n\t\t\t\n\t\t\t"+ + "\n\t\t\n\t
    col1 col2 col3
    abc
    def
    ", data.as(:html) ) + end + + def test_sigma + data = Ruport::DataSet.new(%w[x], :data => [[3],[5],[8],[2]]) + + sum = data.sigma { |r| r["x"] if (r["x"] % 2).zero? } + assert_equal(10, sum) + + sum = data.sigma { |r| r["x"] } + assert_equal(18,sum) + + #check alias + sum = data.sum { |r| r["x"] } + assert_equal(18,sum) + end + + def test_add_columns + d = @data.add_columns("a","b") + assert_equal %w[col1 col2 col3 a b], d.fields + assert_equal %w[col1 col2 col3], @data.fields + assert_equal %w[col1 col2 col3 a b], d[0].fields + assert_equal %w[col1 col2 col3], @data[0].fields + assert_equal nil, d[0]["a"] + d[0]["a"] = "foo" + assert_equal %w[a b c foo] + [nil], d[0].to_a + + d.add_columns!("c") + assert_equal %w[col1 col2 col3 a b c], d.fields + assert_equal %w[col1 col2 col3 a b c], d[0].fields + end + + def test_remove_columns + + d = @data.remove_columns(0) + assert_equal(%w[col2 col3],d.fields) + assert_equal([%w[b c],["","e"]], d.to_a) + + data = @data.remove_columns("col1","col3") + assert_equal(["col2"],data.fields) + assert_equal([["b"],[""]], data.to_a) + + data.remove_columns!("col2") + assert_equal([],data.fields) + assert(data.empty?) + + @data.remove_columns!("col1") + assert_equal(%w[col2 col3], @data.fields) + end + + def test_bracket_equals + expected = DataRow.new(%w[col1 col2 col3], :data => %w[apple banana orange]) + + raw = [ %w[apple banana orange], + { "col1" => "apple", "col2" => "banana", "col3" => "orange" }, + DataRow.new(%w[col1 col2 col3], :data => %w[apple banana orange]) + ] + raw.each do |my_row| + @data[1] = my_row + assert_instance_of(DataRow, @data[1]) + assert_equal(expected, @data[1]) + end + + assert_raise(ArgumentError) { @data[1] = "apple" } + end + + def test_clone + data2 = @data.clone + assert( @data.object_id != data2.object_id ) + assert_equal( @data, data2 ) + data2 << %w[ f o o ] + assert( @data != data2 ) + assert( data2 != @data ) + end + + def test_array_hack + assert_nothing_raised { + [ { :a => :b, :c => :d }, { :e => :f, :g => :h } ].to_ds(%w[a e]) + } + assert_nothing_raised { + [ [1,2,3], [4,5,6], [7,8,9] ].to_ds(%w[a b c]) + } + assert_raise(ArgumentError) { + %w[d e f].to_ds(%w[a b c]) + } + assert_raise(TypeError) { + [1,2,3].to_ds(%w[foo bar soup]) + } + + assert_equal( DataSet.new(%w[a b], :data => [[1,2],[3,4]]), + [[1,2],[3,4]].to_ds(%w[a b]) ) + + assert_equal( DataSet.new(%w[a b], :data => [{ "a" => 1 },{ "b" => 2}]), + [{"a" => 1}, {"b" => 2}].to_ds(%w[a b]) ) + assert_equal( DataSet.new(%w[a b], :data => [DataRow.new(%w[a b], :data => [1,2])]), + [DataRow.new(%w[a b], :data => [1,2])].to_ds(%w[a b]) ) + + # FIXME: Decide whether this test should pass or fail. + # assert_equal( DataSet.new(%w[a b], [DataRow.new(%w[a b], [1,2])]), + # [DataRow.new(%w[a q], [1,2])].to_ds(%w[a b]) ) + + end + + def test_append_chain + data2 = DataSet.new(%w[col1 col2 col3]) + data2.default="" + data2 << %w[ a b c ] << { "col1" => "d", "col3" => "e" } + assert_equal @data, data2 + end + + def test_no_fields + data2 = DataSet.new + data2 << [1,2,3] + data2 << [4,5,6] + assert_equal([1,2,3],data2[0].to_a) + assert_equal([4,5,6],data2[1].to_a) + end +end diff --git a/test/tc_database.rb b/test/tc_database.rb new file mode 100644 index 00000000..f8ada66e --- /dev/null +++ b/test/tc_database.rb @@ -0,0 +1,25 @@ +require "test/unit" +require "ruportlib" + +class TestSqlSplit < Test::Unit::TestCase + def teardown + FileUtils.rm '/tmp/compare.sql' if File.exist?( '/tmp/compare.sql' ) + end + + def test_stonecodeblog_sql + user = 'test' + password = 'password' + dbh = DBI.connect( "dbi:Mysql:test:localhost", user, password ) + dbh.do 'drop database if exists stonecodeblog' + orig_sql = 'test/samples/stonecodeblog.sql' + sql = File.read orig_sql + split = Ruport::Report::SqlSplit.new sql + split.each do |sql| dbh.do( sql ); end + tmp_sql = '/tmp/compare.sql' + md_command = + "mysqldump -u#{ user } -p#{ password } --databases stonecodeblog" + `#{ md_command } > #{ tmp_sql }` + diff = `diff #{ orig_sql } #{ tmp_sql }` + assert( diff == '', diff[0..500] ) + end +end diff --git a/test/tc_document.rb b/test/tc_document.rb new file mode 100644 index 00000000..5cc931a0 --- /dev/null +++ b/test/tc_document.rb @@ -0,0 +1,42 @@ +require "test/unit" +require "ruport" + +class TestDocument < Test::Unit::TestCase + + include Ruport + + def setup + @empty_doc = Format::Document.new :test_doc1 + many_pages = [:p1,:p2,:p3,:p4].map { |p| Format::Page.new(p) } + @populated_doc = Format::Document.new :test_doc2, :pages => many_pages + end + + def test_basics + assert_equal(:test_doc1,@empty_doc.name) + assert_equal(:test_doc2,@populated_doc.name) + assert_equal([],@empty_doc.pages) + assert_equal([:p1,:p2,:p3,:p4],@populated_doc.pages.map { |p| p.name }) + end + + def test_each + page_names = [:p1,:p2,:p3,:p4] + + @populated_doc.each { |p| assert_equal(page_names.shift,p.name) } + assert_equal([],page_names) + + @populated_doc.pages << Format::Page.new(:p5) + page_names = [:p1,:p2,:p3,:p4,:p5] + + @populated_doc.each { |p| assert_equal(page_names.shift,p.name) } + assert_equal([],page_names) + end + + def test_add_page + @empty_doc.add_page :p1 + @populated_doc.add_page :p5, :some_trait => "cool" + assert(@empty_doc.find { |p| p.name.eql?(:p1) }) + assert(@populated_doc.find { |p| p.name.eql?(:p5) and + p.some_trait.eql?("cool") }) + end + +end diff --git a/test/tc_element.rb b/test/tc_element.rb new file mode 100644 index 00000000..9d7154ec --- /dev/null +++ b/test/tc_element.rb @@ -0,0 +1,18 @@ +require "test/unit" +require "ruport" + +class TestElement < Test::Unit::TestCase + include Ruport + def setup + @empty_element = Format::Element.new :test_element + @populated_element = Format::Element.new :test_element2, + :content => "Hello, Element!" + end + + def test_basics + assert_equal(:test_element, @empty_element.name) + assert_equal(:test_element2, @populated_element.name) + assert_equal("Hello, Element!", @populated_element.content) + end + +end diff --git a/test/tc_format.rb b/test/tc_format.rb new file mode 100644 index 00000000..a0d29a9f --- /dev/null +++ b/test/tc_format.rb @@ -0,0 +1,37 @@ +require "test/unit" + +class TestFormat < Test::Unit::TestCase + + def setup + @format = Ruport::Format.new(binding) + end + + def test_filter_erb + @format.content = "<%= 4 + 2 %>" + assert_equal "6", @format.filter_erb + @name = "awesome" + @format.content = " <%= @name %> " + assert_equal " awesome ", @format.filter_erb + end + + def test_filter_red_cloth + if defined? RedCloth + @format.content = "* foo\n* bar" + assert_equal "
      \n\t
    • foo
    • \n\t\t
    • bar
    • \n\t
    ", + @format.filter_red_cloth + end + end + + def filter_ruby + @format.content = "Hash.new" + assert_equal({},@format.filter_ruby) + end + + def test_register_filter + Ruport::Format.register_filter(:lower) { |content| content.downcase } + @format.content = "FoO" + assert_equal("foo", @format.filter_lower) + end + +end + diff --git a/test/tc_format_engine.rb b/test/tc_format_engine.rb new file mode 100644 index 00000000..26fb512a --- /dev/null +++ b/test/tc_format_engine.rb @@ -0,0 +1,108 @@ +begin require 'rubygems'; rescue LoadError; nil end +require 'ruport' +require 'test/unit' + +class MockPlugin < Ruport::Format::Plugin + + renderer(:table) { "#{rendered_field_names}#{data}" } + + format_field_names { "#{data.fields}" } + + renderer(:document) { data } + + register_on :table_engine + register_on :document_engine + + rendering_options :red_cloth_enabled => true, + :erb_enabled => true + +end + +class TestTabularFormatEngine < Test::Unit::TestCase + + include Ruport + + def setup + @engine = Format::Engine::Table.dup + end + + def test_basic_render + @engine.plugin = :mock + @engine.data = [[1,2,3],[4,5,6],[7,8,9]] + assert_equal( "#{@engine.data}", @engine.render ) + end + + def test_render_without_field_names + @engine.plugin = :mock + @engine.show_field_names = false + + @engine.data = [[1,2,3],[4,5,6],[7,8,9]] + assert_equal "#{@engine.data}", @engine.render + end + + def test_simple_interface + expected = "#{[[1,2],[3,4]]}" + actual = Format.table(:plugin => :mock, :data => [[1,2],[3,4]]) + assert_equal(expected,actual) + end + + def test_rewrite_column + a = @engine.dup + a.plugin = :mock + + a.data = [[1,2],[3,4]].to_ds(%w[a b]) + a.rewrite_column("a") { |r| r["a"] + 1 } + assert_equal([[2,2],[4,4]].to_ds(%w[a b]),a.data) + assert_nothing_raised { a.render } + + a.data = [[5,6],[7,8]] + a.rewrite_column(1) { "apple" } + assert_equal [[5,"apple"],[7,"apple"]], a.data + assert_nothing_raised { a.render } + end + + def test_num_columns + @engine.data = [[1,2,3,4],[5,6,7,8]] + assert_equal(4,@engine.num_cols) + + @engine.data = [[1,2,3],[4,5,6]].to_ds(%w[a b c]) + assert_equal(3,@engine.num_cols) + end + +end + +class TestDocumentFormatEngine < Test::Unit::TestCase + def setup + @engine = Format::Engine::Document.dup + end + + def test_basic_render + @engine.plugin = :mock + @engine.red_cloth_enabled = true + @engine.erb_enabled = true + @engine.data = "* <%= 3 + 2 %>\n* <%= 'apple'.reverse %>\n* <%= [1,2][0] %>" + h = "
      \n\t
    • 5
    • \n\t\t
    • elppa
    • \n\t\t
    • 1
    • \n\t
    " + assert_equal h, @engine.render + end + + def test_simple_interface + + d = "*<%= 'apple'.reverse %>*" + opts = { :data => d, :plugin => :mock } + + a = Format.document opts + assert_equal "

    elppa

    ", a + + a = Format.document opts.merge!({:red_cloth_enabled => false}) + assert_equal "*elppa*", a + + a = Format.document opts.merge!({:erb_enabled => false}) + assert_equal d, a + + a = Format.document :data => d, :plugin => :mock + assert_equal "

    elppa

    ", a + + end +end + + diff --git a/test/tc_page.rb b/test/tc_page.rb new file mode 100644 index 00000000..ea8dae54 --- /dev/null +++ b/test/tc_page.rb @@ -0,0 +1,42 @@ +require "test/unit" +require "ruport" + +class TestPage < Test::Unit::TestCase + include Ruport + def setup + @empty_page = Format::Page.new :test_page1 + sections = { :s1 => Format::Section.new(:s1), :s2 => Format::Section.new(:s2) } + @populated_page = Format::Page.new :test_page2, + :sections => sections + end + + def test_basics + assert_equal(:test_page1, @empty_page.name) + assert_equal(:test_page2, @populated_page.name) + assert_equal([],[:s1,:s2]-@populated_page.map { |s| s.name }) + assert_equal({},@empty_page.sections) + end + + def test_each + section_names = [:s1,:s2] + @populated_page.each { |s| section_names -= [s.name] } + assert_equal([],section_names) + @populated_page << Format::Section.new(:s3) + section_names = [:s1,:s2,:s3] + @populated_page.each { |s| section_names -= [s.name] } + assert_equal([],section_names) + end + + def test_add_function + @populated_page.add_section :s3 + @populated_page.add_section :s4, :content => "Hello from Section!" + assert(@populated_page.find { |s| s.name.eql?(:s3) }) + assert(@populated_page.find { |s| s.name.eql?(:s4) and + s.content.eql?("Hello from Section!")} ) + + end + def test_brackets + assert_equal(:s1,@populated_page[:s1].name) + assert_equal(:s2,@populated_page[:s2].name) + end +end diff --git a/test/tc_plugin.rb b/test/tc_plugin.rb new file mode 100644 index 00000000..7030e3bc --- /dev/null +++ b/test/tc_plugin.rb @@ -0,0 +1,184 @@ +require "test/unit" +require "ruport" + + +include Ruport + +# a sample plugin to test the system +class TSVPlugin < Format::Plugin + + require "fastercsv" + + format_field_names do + FasterCSV.generate(:col_sep => "\t") { |csv| csv << data.fields } + end + + renderer :table do + rendered_field_names + + FasterCSV.generate(:col_sep => "\t") { |csv| data.each { |r| csv << r } } + end + + register_on Format::Engine::Table + +end + +class CSVPluginTest < Test::Unit::TestCase + + def test_basic + a = Format.table_object :plugin => :csv, :data => [[1,2],[3,4]] + assert_equal("1,2\n3,4\n",a.render) + + a.data = a.data.to_ds(%w[a b]) + assert_equal("a,b\n1,2\n3,4\n",a.render) + + a.show_field_names = false + assert_equal("1,2\n3,4\n", a.render) + end + +end + +class PDFPluginTest < Test::Unit::TestCase + + def test_ensure_fails_on_array + a = Format.table_object :plugin => :pdf, :data => [[1,2],[3,4]] + assert_raise(RuntimeError) { a.render } + + a.data = a.data.to_ds(%w[a b]) + assert_nothing_raised { a.render } + + #FIXME: Engine should be further duck typed so this test will pass + #a.data = [{ "a" => "b", "c" => "d" },{"a" => "f", "c" => "g"}] + #assert_nothing_raised { a.render } + end + + def test_hooks + a = Format.table_object :plugin => :pdf, + :data => [[1,2],[3,4]].to_ds(%w[a b]) + y = 0 + a.active_plugin.pre = lambda { |pdf| + assert_instance_of(PDF::Writer,pdf); + y = pdf.y + } + a.active_plugin.post = lambda { |pdf| + assert_instance_of(PDF::Writer,pdf); assert(pdf.y < y) + } + assert_nothing_raised { a.render } + end + +end + +class HTMLPluginTest < Test::Unit::TestCase + + def test_basic + a = Format.table_object :plugin => :html, :data => [[1,2],[3,nil]] + assert_equal("\n\t\t\n\t\t\t\n\t\t\t"+ + "\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
    12
    3 "+ + "
    ",a.render) + a.data = a.data.to_ds(%w[a b]) + assert_equal("\n\t\t\n\t\t\t\n\t\t\t"+ + "\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
    a b
    1"+ + "2
    3"+ + " 
    ",a.render) + a.show_field_names = false + assert_equal("\n\t\t\n\t\t\t\n\t\t\t"+ + "\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
    12
    3 "+ + "
    ",a.render) + end +end + + +#this test is intended to ensure proper functionality of the plugin system. +class NewPluginTest < Test::Unit::TestCase + + def test_basic + a = Format.table_object :plugin => :tsv, :data => [[1,2],[3,4]] + assert_equal("1\t2\n3\t4\n",a.render) + + a.data = a.data.to_ds(%w[a b]) + assert_equal("a\tb\n1\t2\n3\t4\n",a.render) + + a.show_field_names = false + assert_equal("1\t2\n3\t4\n",a.render) + end + +end + +class TextPluginTest < Test::Unit::TestCase + + def test_basic + + tf = "+-------+\n" + + a = Format.table_object :plugin => :text, :data => [[1,2],[3,4]] + assert_equal("#{tf}| 1 | 2 |\n| 3 | 4 |\n#{tf}",a.render) + + a.data = a.data.to_ds(%w[a b]) + assert_equal("#{tf}| a | b |\n#{tf}| 1 | 2 |\n| 3 | 4 |\n#{tf}", a.render) + + end + + def test_max_col_width + a = Format.table_object :plugin => :text, :data => [[1,2],[300,4]] + assert_equal(3,a.active_plugin.max_col_width(0)) + assert_equal(1,a.active_plugin.max_col_width(1)) + + a.data = [[1,2],[300,4]].to_ds(%w[a b]) + + assert_equal(3,a.active_plugin.max_col_width("a")) + assert_equal(1,a.active_plugin.max_col_width("b")) + assert_equal(3,a.active_plugin.max_col_width(0)) + + a.data = [[1,2],[3,40000]].to_ds(%w[foo bazz]) + assert_equal(3,a.active_plugin.max_col_width("foo")) + assert_equal(5,a.active_plugin.max_col_width("bazz")) + + assert_equal(3,a.active_plugin.max_col_width(0)) + assert_equal(5,a.active_plugin.max_col_width(1)) + end + + def test_table_width + a = Format.table_object :plugin => :text, :data => [[1,2],[300,4]] + assert_equal(4,a.active_plugin.table_width) + + a.data = [[1,2],[3,40000]].to_ds(%w[foo bazz]) + assert_equal(8,a.active_plugin.table_width) + end + + def test_hr + a = Format.table_object :plugin => :text, :data => [[1,2],[300,4]] + + assert_equal("+---------+\n",a.active_plugin.hr) + + + a.data = [[1,2],[3,40000]].to_ds(%w[foo bazz]) + assert_equal "+-------------+\n", a.active_plugin.hr + + end + + def test_centering + tf = "+---------+\n" + + a = Format.table_object :plugin => :text, :data => [[1,2],[300,4]] + assert_equal("#{tf}| 1 | 2 |\n| 300 | 4 |\n#{tf}",a.render) + + tf = "+------------+\n" + a.data = a.data.to_ds(%w[a bark]) + assert_equal("#{tf}| a | bark |\n#{tf}| 1 | 2 |\n"+ + "| 300 | 4 |\n#{tf}",a.render) + + end + + def test_wrapping + + a = Format.table_object :plugin => :text, :data => [[1,2],[300,4]] + + #def SystemExtensions.terminal_width; 10; end + a.active_plugin.right_margin = 10 + assert_equal("+------->>\n| 1 | >>\n| 300 | >>\n+------->>\n",a.render) + + a = Format.table_object :plugin => :text, :data => [[1,2],[300,4]] + assert_equal(nil,a.active_plugin.right_margin) + end + +end + diff --git a/test/tc_query.rb b/test/tc_query.rb new file mode 100644 index 00000000..a9cce635 --- /dev/null +++ b/test/tc_query.rb @@ -0,0 +1,69 @@ +require "test/unit" +require "ruport" +class TestQuery < Test::Unit::TestCase + + + def setup + Ruport::Config.source :default, + :dsn => "ruport:test", :user => "greg", :password => "apple" + + Ruport::Config.source :alternate, + :dsn => "ruport:test2", :user => "sandal", :password => "harmonix" + + @query1 = Ruport::Query.new "select * from foo", :cache_enabled => true + @query1.cached_data = [[1,2,3],[4,5,6],[7,8,9]] + end + + + def test_result + assert_nothing_raised { @query1.result } + assert_equal([[1,2,3],[4,5,6],[7,8,9]],@query1.result) + end + + def test_each + data = [[1,2,3],[4,5,6],[7,8,9]] + @query1.each do |r| + assert_equal(data.shift,r) + end + data = [1,4,7] + @query1.each do |r| + assert_equal(data.shift,r.first) + end + assert_raise (LocalJumpError) { @query1.each } + end + + def test_select_source + + assert_equal( "ruport:test", @query1.instance_eval("@dsn") ) + assert_equal( "greg", @query1.instance_eval("@user") ) + assert_equal( "apple", @query1.instance_eval("@password") ) + + @query1.select_source :alternate + + assert_equal( "ruport:test2", @query1.instance_eval("@dsn") ) + assert_equal( "sandal", @query1.instance_eval("@user") ) + assert_equal( "harmonix", @query1.instance_eval("@password") ) + + @query1.select_source :default + + assert_equal( "ruport:test", @query1.instance_eval("@dsn") ) + assert_equal( "greg", @query1.instance_eval("@user") ) + assert_equal( "apple", @query1.instance_eval("@password") ) + + end + + def test_generator + assert @query1.generator.kind_of?(Generator) + gen = @query1.generator + assert_equal [1,2,3], gen.next + assert_equal [4,5,6], gen.next + assert_equal [7,8,9], gen.next + assert_raise(EOFError) { gen.next } + end + + def test_to_dataset + # query needs to be re-worked for a test like this + # to be written + end + +end diff --git a/test/tc_report.rb b/test/tc_report.rb new file mode 100644 index 00000000..1a9ac850 --- /dev/null +++ b/test/tc_report.rb @@ -0,0 +1,31 @@ +#tc_report.rb +# +# Created by Gregory Thomas Brown on 2005-08-09 +# Copyright 2005 (Gregory Brown) All rights reserved. + +require "test/unit" +require "ruport" +class TestReport < Test::Unit::TestCase + include Ruport + + def setup + @report = Report.new + end + + def test_render + result = @report.render "<%= 2 + 3 %>", + :filters => [:erb] + assert_equal("5",result) + + if defined? RedCloth + result = @report.render '"foo":http://foo.com', + :filters => [:red_cloth] + + assert_equal('

    foo

    ',result) + result = @report.render %{"<%= 2 + 3%>":http://foo.com }, + :filters => [:erb, :red_cloth] + assert_equal('

    5

    ',result) + end + end + +end diff --git a/test/tc_ruport.rb b/test/tc_ruport.rb new file mode 100644 index 00000000..e4daaa2b --- /dev/null +++ b/test/tc_ruport.rb @@ -0,0 +1,59 @@ +require "test/unit" +require "fileutils" +require "stringio" +require "ruport" + +class TestRuport < Test::Unit::TestCase + + def setup + Ruport::Config.log_file "test/complain.log" + @output = StringIO.new + end + + def test_file_created + assert(File.exist?("test/complain.log")) + end + + def test_fatal + assert_raise(RuntimeError) { + Ruport::complain "Default problem", :status => :fatal, + :output => @output + } + @output.rewind + assert_equal("[!!] Default problem\n", @output.read) + end + + def test_fatal_log_only + assert_raise(RuntimeError) { + Ruport::complain "Default problem", :status => :fatal, + :output => @output, + :level => :log_only + } + @output.rewind + assert_equal("",@output.read) + + end + + def test_warn + assert_nothing_raised { + Ruport::complain "Default problem", :output => @output + } + @output.rewind + assert_equal("[!!] Default problem\n",@output.read) + end + + def test_warn_log_only + assert_nothing_raised { + Ruport::complain "Default problem", :output => @output, + :level => :log_only + } + @output.rewind + assert_equal("",@output.read) + end + + def teardown + Ruport::Config.class_eval("@@logger").close + FileUtils.rm("test/complain.log") + end + +end diff --git a/test/tc_section.rb b/test/tc_section.rb new file mode 100644 index 00000000..170ceaaf --- /dev/null +++ b/test/tc_section.rb @@ -0,0 +1,45 @@ +require "test/unit" +require "ruport" + +class TestSection < Test::Unit::TestCase + include Ruport + def setup + elements = { :e1 => Format::Element.new(:e1), + :e2 => Format::Element.new(:e2) } + @empty_section = Format::Section.new :test_section1 + @populated_section = Format::Section.new :test_section2, + :elements => elements + + end + + def test_basics + assert_equal( :test_section1, @empty_section.name ) + assert_equal( :test_section2, @populated_section.name ) + assert_equal( [], [:e1,:e2]-@populated_section.map { |e| e.name } ) + end + + def test_each + element_names = [:e1,:e2] + @populated_section.each { |e| element_names -= [e.name] } + assert_equal([],element_names) + + element_names = [:e1, :e2, :e3] + @populated_section << Format::Element.new(:e3) + @populated_section.each { |e| element_names -= [e.name] } + assert_equal([],element_names) + end + + def test_add_element + @empty_section.add_element :e1 + @empty_section.add_element :e2, :content => "Hello from Element!" + assert(@empty_section[:e1]) + assert(@empty_section[:e2]) + assert_equal("Hello from Element!",@empty_section[:e2].content) + end + + def test_brackets + assert_equal(:e1,@populated_section[:e1].name) + assert_equal(:e2,@populated_section[:e2].name) + end + +end diff --git a/test/tc_sql_split.rb b/test/tc_sql_split.rb new file mode 100644 index 00000000..0d805158 --- /dev/null +++ b/test/tc_sql_split.rb @@ -0,0 +1,18 @@ +require "test/unit" +require "ruport" +class TestSqlSplit < Test::Unit::TestCase + include Ruport + + def test_sql_split1 + sql = File.read 'test/samples/ruport_test.sql' + split = Query::SqlSplit.new sql + assert_equal( 'SELECT * FROM ruport_test', split.last ) + end + + def test_sql_split2 + sql = "SELECT * FROM ruport_test" + split = Query::SqlSplit.new sql + assert_equal( 1, split.size ) + assert_equal( sql, split.first ) + end +end diff --git a/test/ts_all.rb b/test/ts_all.rb new file mode 100644 index 00000000..7a496d4c --- /dev/null +++ b/test/ts_all.rb @@ -0,0 +1,9 @@ +require "test/unit" +require "test/ts_format" +require "test/tc_ruport" +require "test/tc_sql_split" +require "test/tc_query" +require "test/tc_config" +require "test/tc_report" +require "test/tc_data_set" +require "test/tc_data_row" diff --git a/test/ts_format.rb b/test/ts_format.rb new file mode 100644 index 00000000..e0675e9f --- /dev/null +++ b/test/ts_format.rb @@ -0,0 +1,7 @@ +require "test/tc_format" +require "test/tc_document" +require "test/tc_element" +require "test/tc_section" +require "test/tc_page" +require "test/tc_format_engine" +require "test/tc_plugin" diff --git a/util/release/raa.rb b/util/release/raa.rb new file mode 100644 index 00000000..8d98599f --- /dev/null +++ b/util/release/raa.rb @@ -0,0 +1,13 @@ +require "rubygems" +require "ruport" +require "mechanize" + +(puts "Need password"; exit) unless ARGV[0] + +frontpage = 'http://raa.ruby-lang.org/update.rhtml?name=ruport' +agent = WWW::Mechanize.new +page = agent.get frontpage +form = page.forms[1] +form.fields.name("pass").value = ARGV[0] +form.fields.name("version").value = (Ruport::VERSION =~ /(\d.\d.\d)/ && $1) +page = agent.submit(form, form.buttons[0])