tag:blogger.com,1999:blog-31857105666177254382014-10-06T19:46:35.940-07:00NPEREZ's Perl MusingsNPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.comBlogger59125tag:blogger.com,1999:blog-3185710566617725438.post-60953846866704530822013-01-19T09:20:00.001-08:002013-01-19T09:20:59.431-08:00CloudPANIt is funny that my returning post would concern clouds as I am actually writing this while above real clouds over nine kilometers up. While attending the Orlando Perl Workshop (aka. Perl Oasis), I was a selected speaker for a short talk about one of my modules: CloudPAN. I figured I would do a little bit of a write up about CloudPAN and why I think it is useful and how it could be even more useful. <br /><br />But before getting to the point of talking about CloudPAN we need to take a slight detour and talk about the awesomeness that is MetaCPAN. If you've been living under rock for the past couple of years, you might not know that MetaCPAN is quite possibly one of best things to evolve out of the Perl world recently. MetaCPAN is important to the Perl community for a number of reasons including: it provides a really simple API for interrogating the CPAN indicies, it also provides an open source web platform for displaying CPAN information. <br /><br />The project was born out of frustration at search.cpan.org. I don't have all of the history available to me at the moment, but it is sufficient to say that not every one was happy with how search.cpan.org worked. Not only that, people couldn't provide patches or even fork it if they wanted. <br /><br />So how does MetaCPAN provide the magic that it does? It actually mostly relies upon ElasticSearch. ElasticSearch is a fantastic tool built on top of Lucene that provides all sorts of awesome scalability via clustering, multiple indexes, and basically takes Lucene and applies it in a tremendous way. In fact, the API that MetaCPAN provides is really just a thin wrapper around what ElasticSearch expects with a few bells and whistles that let us do things that enable modules like CloudPAN. <br /><br />CloudPAN was really just a silly idea that came about during the QA Hackathon 2012 in Paris. David Golden wanted to enable the CPAN.pm client to talk to MetaCPAN in order to reduce it's footprint. If you can directly query some external service about available distributions and modules, there is no need to download large gzipped files and build out local caches. But! There was a snag. The MetaCPAN::API module actually had a bunch of deps that aren't exactly core. So part of my time spent was implementing MetaCPAN::API::Tiny that only relied upon HTTP::Tiny to talk to MetaCPAN. With HTTP::Tiny getting into core, that meant the CPAN.pm client could grow up without external deps. <br /><br />And it was during that work that I noticed a rather curious method to the MetaCPAN API: file. This is how the source display works on the site, basically. The file api basically opens up the distribution, finds the file, and slurps in for you. The gears started turning at that point. Could I really write a dumb INC hook that made a call to MetaCPAN for the source to modules that weren't installed locally? Thus, CloudPAN was born. I aptly demonstrated pulling in Moo, building classes, using those classes, etc. all without having actually installing Moo. <br /><br />It took me sometime to do a little clean up and throw together some docs. After all, it was just a useless hack (the most useless at the QA Hackathon). But then I found myself using it on a regular basis when trying out modules to get a feel for how they worked and if they provided the right solution to the problem I had. I figured people would also like the ability to try out modules. So it ended up on CPAN. <br /><br />The current version even has the option to persist what you've downloaded previously. And when using it again, it will load from that location. No need to download the entire depenency graph again for a particular module just because you accidentally CTRL-D'd your re.pl shell. <br /><br />I'd like to grow it up further and perhaps teach it do other things, such as fetching from your own local MetaCPAN installation. And maybe even have it do authentication and verification of the remote. This could eventually end up as a way to distribute pure Perl deps for scripts on the first run. <br /><br />But I'll save that for another day. Give CloudPAN a try if you find yourself wanting to evaluate modules, but don't want to clutter your local perl installation with modules you'll never use. <br /><br />Nicholas Perezhttps://plus.google.com/101413896957719356811noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-61553577171012137102013-01-15T22:29:00.000-08:002013-01-15T22:29:20.672-08:00Returning to the frayExpect me to start updating this blog again. I plan on contributing to the Moe project (<a href="http://github.com/stevan/moe">http://github.com/stevan/moe</a>). I am also doing some courses from Coursera. Some work stuff might bleed into this blog as well (including when my posts are published on our official work blog: http://blog.booking.com)<br /><br />Quick update: I've moved to the Netherlands. If you remember, I had quite an atrocious experience at the hands of the TSA. That experience stuck. From that point on, I knew my country had given up on its citizenry. I eventually sought out employment elsewhere-- As in, not within the bounds of the US.<br /><br />I am now a team lead at Booking.com working on some of the more critical infrastructure pieces. We live in beautiful Amsterdam. I had taken a break of sorts of doing any serious open source work. I am now going to change that a little bit.<br /><br />With the announcement of Moe (and my photobomb making a cameo in Stevan Little's slide deck[<a href="https://speakerdeck.com/stevan_little/perl-is-not-dead-it-is-a-dead-end?slide=164">https://speakerdeck.com/stevan_little/perl-is-not-dead-it-is-a-dead-end?slide=164</a>]), I've started the trek to learning enough Scala to be dangerous. OPW2013 really seemed to energize me if only because getting together with a bunch of really smarty-pants hackers always seems to have that effect.<br /><br />My inaugural return post will be about CloudPAN if only because it is fresh on my mind. I gave a talk at OPW2013 about this nifty little hack that fell out of my attending the QA Hackathon last year. And from that point on, I'll try to do a weekly update.<br /><br />It might be more often. It might be less often. But I want to at least average one post a week.<br /><br />So expect a post later this week. I have a long flight tomorrow and I intend to do some hacking and also some writing.Nicholas Perezhttps://plus.google.com/101413896957719356811noreply@blogger.com1tag:blogger.com,1999:blog-3185710566617725438.post-38302238570629810922010-10-12T07:57:00.000-07:002010-10-12T08:05:15.629-07:00Sensible LetterI just contacted my House representative with the following letter. It is a more properly measured correspondence, filled with less ragespeak and more call-to-action words. I wait patiently with baited breath for my form letter response thanking me for contacting him about gun control laws. Sigh.<br /><br /><br />----<br /><br />Regarding TSA experience:<br /><br />Mr. Joe Barton:<br /><br />After refusing to be have my body irradiated and naked pictures shown to "highly trained security professionals", I was given an "enhanced pat down" which had no patting involved, only lots of unwanted rubbing and general molestation. This is not security. This is blatant violation of my person. Then when I voiced my dissent on this process using strong language (as any reasonable person would do under this circumstance), I was "invited" to have a conversation with a manager in a suit and a uniformed law enforcement officer. At this point, I asked if I was being detained or if I was free to go. My question was not answered until after the LEO and manager arrived where they attempted to intimidate me into surrendering my first amendment protected right to free speech. I was then accused of causing a "scene" and was given the threat of "not flying." I argued for my protected right to free speech, but then realized that this was not going to change anything. I reiterated my previous detainment questions and was told I was free to go so I left immediately.<br /><br />After having such a terrible experience at the hands of these poorly trained "professionals," I urge you not to further support any bills that will increase funding to the TSA for any additional staff or equipment and to vote for any bills that limit the TSA's governance. Situations like mine are not increasing airport security, they are violating individuals rights and persons.<br /><br />Sincerely,<br /><br />Nicholas Perez<br />[contact information redacted]NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com3tag:blogger.com,1999:blog-3185710566617725438.post-1707377647065691902010-10-11T14:14:00.000-07:002010-10-11T14:33:06.201-07:00Fuck you TSAI refused to be irradiated, so instead I was molested. And because I had the unmitigated gall to speak my fuckin' mind about this I was then "invited" to have a conversation with a couple of fuckin' goons. Did you know that these assholes think they are protecting this country? And that because I said "Mother Fucker" to one of them that I was causing a "scene." Of course I am you baffoon in a blue shirt. Causing a "scene" is not against the law. MOTHER FUCKER is constitutionally protected speech. And to educate TSA a bit more, saying FUCK is not the same thing as shouting fire in a theater. Learn the difference before trotting out that tired old line to justify your horrible argument for censorship. A note to anyone else ever in a similar situation: take pictures and remember the magic words: "Am I being detained? Am I free to go?" if they answer no to the first or yes to the second, just walk away. These fucktwats are simply not worth the time.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com1tag:blogger.com,1999:blog-3185710566617725438.post-35006434192677855232010-10-11T10:58:00.000-07:002010-10-11T12:31:22.020-07:00Pittsburg Perl Workshop and StuffSo this past weekend was neat. I love coming to workshops even if it is a very compressed time frame. My take away from this workshop is that I have wanted to do some hardware hacking for quite sometime, but never got around to purchasing an arduino board with some addons. So perhaps in the next few months I'll start working on something. Something network-enabled and practical. We'll see. Not that there is any direct Perl that is running on these boards (the code is some kind of C-derivative with a compiler/IDE), but you can use Perl to talk to it over a serial port or whatever. <br /><br />And while I had set off to write a POE::Filter that spoke the Minecraft SMP protocol this weekend, it never materialized. Instead, I started working on modernizing yet another one of my modules: POE::Filter::XML::RPC. The problem is that I am subclassing XML::LibXML::Element and it doesn't play nice with subclassing. So I wanted to extend it, using Moose and advise all of the methods I could find that returned other Elements or Nodes. That was going to be a very very large copy/paste job. MooseX::Declare didn't support shortcut method modifiers (around [qw/foo bar baz/] {...}). So last night and this morning, I hacked that support into MooseX::Method::Signatures (and its use is transparent to MooseX::Declare). All so I could have a single line of copy paste instead of a lot more. I'm so god damn lazy sometimes.<br /><br />And interesting side effect of adding this functionality into MXMS: you can have stringified array references as method names, heh. In MXD, the declarators are given specific meaning and functionality via callbacks so that the array references are absorbed and passed on to the meta munging methods that add the method modifications.<br /><br />Anyhow, I've made the pull requests to rafl, and just waiting for him to incorporate and release.<br /><br />If anyone is ever on the fence on whether or not to attend Perl workshops, let me settle that for you: GO. It is an awesome time. Everything from learning to networking opportunities await any avid Perl programmer. And this gave me ideas for next year's set of talks that I need to write (I didn't speak at this workshop, I was all tuckered out from speaking earlier in the year).<br /><br />Hope to see new faces at OPW in January.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-62590776866501173342010-10-07T08:16:00.000-07:002010-10-07T08:47:23.913-07:00Of Modernizing My Own ModulesIt is amazing how much bitrot accumulates over the course of a year and a half. And also amazing to see the state of the art move so rapidly.<br /><br />Dist::Zilla has a break-neck pace of development. While my old dist.ini files still run without issue, so many new and awesomer features have crept into the core. This makes it worth while to revisit your dist.ini files even if changes are minor in your project. AutoPrereqs is smart enough now that only a few MooseX::Declare edge cases are missed. PodWeaver is just plain awesome and if you aren't using it to generate your POD something is wrong. The introduction of the Basic PluginBundle means you have even more fine grained control over your distributions. If you are looking for a basic template of a dist.ini that you can use in your own projects, take a gander at mine <a href="http://xrl.us/bh3y2k">http://xrl.us/bh3y2k (Link to nickandperla.net)</a>. And if you've looked at my documentation for modules and like my POD style, here is my weaver.ini too: <a href="http://xrl.us/bh3y2x">http://xrl.us/bh3y2x (Link to nickandperla.net)</a><br /><br />And while I like modernizing my code, this latest push for several modules wasn't entirely voluntary. Newer perls have broken MooseX::Method::Signatures (and likely Devel::Declare itself). This affects a large chunk of my code as I have grown very accustomed to the idea of having constraints on my methods. The brokenness lies in the parsing itself. Seems it doesn't like there to be a newline between the method declaration and the opening brace. In other words, to avoid this problem you have to use ugly K&R-style braces. So that is what I have been doing for a lot of modules, moving the braces.<br /><br />But, other modules, it was good-old-fashioned modernization. A new version of POE::Filter::XML was released last night that brings it into the modern era. It was an old module in the sense that the distribution still had distribution artifacts stored in source control. POD was all done by hand and at the bottom of the files. POE::Filter::XML::Handler didn't even have any documentation. I wrote my own god damn accessors. It was ugly. <br /><br />So I put in the time to make things right. It should be backwards compatible (to an extent). Node was updated to be a proper Moose class (using MooseX::NonMoose::InsideOut) that extends XML::LibXML::Element. This lets us, among other things, override methods and call super() when appropriate (making sure that methods that return Elements actually return Nodes). All of the custom constructors went away. And the logic was greatly simplified by using attributes with native traits. The code simply /looks/ modern. <br /><br />Next on the chopping block is POE::Component::Jabber. I plan on stripping the functionality down to a mere Role. Also, I am going to remove support for pre-XMPP connections, and server specific connection types for components. I want a mean, lean Role that can be composed cleanly and easily. I also want it to be easily extensible too, so if other enterprising developers /want/ those other connection features, they can implement them and use them without monkey-patching my code.<br /><br />In other words, POE::Component::Jabber is disappearing and will be replaced with POEx::Role::XMPPClient in the not too distant future.<br /><br />So don't be afraid of looking back on your modules and vomiting a little in your mouth. It doesn't take /that/ much time to modernize them. That way the next time you want to use something and find a bug in it, you won't cringe when you need to fix it and do a quick release.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-52229689976612329952010-06-17T02:25:00.000-07:002010-06-17T02:51:10.554-07:00Of Perl JobsSo I've been on the receiving end of a couple of unsolicited emails regarding open positions in a couple of companies. And it is odd to get these kinds of emails, especially when they aren't from recruiters, but from the hiring manager themselves. It is a little flattering, to say the least.<br /><br />That said, the one thing these emails/companies/etc have in common is that the culture that makes me the most productive just isn't there, mentioned, or encouraged. Now, one could say my <a href='http://iinteractive.com'>workplace</a> is a little unorthodox since we are all virtual, but in the grand scheme of things, it isn't the virtual part that matters the most. What matters the most is that our company isn't isolated from the community. <br /><br />We actively participate on <a href='http://search.cpan.org'>CPAN</a>, <a href='http://www.irc.perl.org'>IRC</a>, <a href='http://perlmonks.org'>message boards</a>, and most of the major modern Perl projects mailing lists. In our day to day, our communications circle encompasses the community. We contribute to important projects. We release generic solutions to problems we've solved in the course of our business. And we make use of others' solutions as they have released them. Frankly, I don't see how we could get much done if we /didn't/ invest as heavily in the community as we do. <br /><br />So when I get emails or see job postings from companies that fail to mention any level of community participation, they are placed into the round file holder. And I am not alone in this. The people that I would consider my peers, other community members, the "rockstars" for which these job postings are seeking, aren't about to jump ship from a culture of shared commons and productivity to one of isolation and take-but-not-give-back. <br /><br />So in the future, if you recruiters or hiring managers wish to cater to truly senior level Perl developers, Perl developers working with the state-of-the-art, please take the time to understand that there are deeper motivations than merely salary, location, or even the business domain.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com2tag:blogger.com,1999:blog-3185710566617725438.post-89960464928664332842010-06-10T12:00:00.000-07:002010-06-10T12:17:55.362-07:00POEx::WorkerPool and PokeI am pleased to announce a new release of POEx::WorkerPool and a new TRIAL release of Poke. POEx::WorkerPool is a multi-process framework for implementing the worker pattern in Perl making use of POE as the backend. All you need to do is compose the Job role and you're golden.<br /><br />Poke is a monitoring framework that uses POEx::WorkerPool for its foundation. Poke gives you the ability to simply compose it's Job role, and add the job's configuration to the config.ini. Your jobs can do anything. Need to run a complex query once an hour to make sure a queue is getting cleared? No problem. Want to ping a server to see if it is alive? Easy. By default, Poke comes with simple HTTP Job that merely hits a URI to see if 200 is returned.<br /><br />Poke wouldn't be much if it was just a subclass of WorkerPool, it is much more than that. Each job status is committed to a database along with start and stop times. The status of jobs can be viewed through the embedded web component that lets you view the last status result, and the previous 10 for a particular job. The system itself can be meta-monitored by tailing the syslog output. And just about every part of the system is configurable from the number of worker processes, to the level of output to syslog. <br /><br />Configuring jobs is also very easy as well. Attributes in the job's config correspond to actual attributes on the job class. Your config can contain any number of uniquely named jobs each configured to fire on it's own frequency.<br /><br />This is another TRIAL release because I haven't written proper tests and fully documented the guts. Some things are still in flux as well when it comes to the embedded web portion. There are some features in HTML::Zoom and Web::Simple that haven't reached release yet and I didn't want to depend on git versions to run this release. Ultimately, I'd like to be able to control job execution from the web portion (pause, one-time run, delete, add), but I am not sure that will be in the initial finished release.<br /><br />Please feel free to download the TRIAL release and play with it. It comes with an example config. From the project directory, simply run <span style='font-family: monospace'>perl -Ilib bin/poked --config example.ini --no_fork 1</span> and monitor daemon syslog output, and connect to http://localhost:12345/ to see the web output.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-61497925470018300112010-05-21T08:45:00.000-07:002010-05-21T10:10:10.777-07:00Iron Man Fail + Xenoterracide is a whiny bitchSo, I have failed. I failed to post within the given window for the Iron Man challenge. There is a reason for this: I lost interest. And I generally lost interest in the Perl community for a few weeks. But, I only did because I had other interests and activities consume my time like archery, fletching, and starting my own business. That said, I felt it was important to let you, my dear readers, know that I didn't die and that I will continue to blog, but perhaps not to the frequency required for the Iron Man challenge. <br /><br />Now on to other matters. Lately, a tiny subset of the Perl community is in an uproar over Xenoterracide's <a href='http://xenoterracide.blogspot.com/'>blog</a> and for good reason. He is a whiny bitch. I find it laughable that Caleb feels entitled to our labor. The whole kerfuffle started when he bitched about rjbs' wonderful Dist::Zilla. His complaint was that it lacked documentation and felt that rjbs owed it to him or he shouldn't have released the software in the first place. In Caleb's world view, all released software should have documentation and that he shouldn't have to lift a finger for a project he finds useful. Then when people rationally explained that he ate too many paint chips as a kid and that isn't how the world works, he then decided to stick his fingers in his ears and start shouting "I'M NOT LISTENING LALALALA." Now he is hellbent on convincing people that his delusion is how the world actually is. So with all this back and forth, what has Caleb actually contributed? Nothing.<br /><br />People, this is what we call a leech. Now here comes the philosophical rant.<br /><br />I've been working with and developing Free Software for a number of years. I generally believe in the share and share-alike principle when it comes to Free Software. I release my code under GPL and GPL-compatible licenses because I feel the solutions are general enough that others may find them useful for their stated purpose. Others do the same. And in the end we build up a giant collection of useful software that we can all view, modify and redistribute our source modifications. In business, this makes sense. As an industry (software development that is), we've developed a set of tools to accomplish the most generic goals that we all have. This allows us to successfully focus on the needs of the business in developing our solutions and saves costs on redeveloping the wheel the next time we need an asynchronous event framework like POE. We are a community of peers. We participate in a commons toward shared goals. I stand on the shoulders of giants when I contribute, as do many others. It is a great system and it works really well. But, there are a few bad apples. The leeches.<br /><br />Leeches drain resources from successful projects with their inanity because they do not contribute any useful labor back to the project they are using. These are the people that bitch and whine in IRC without putting forth the basic effort to understand the project and its purpose. These are the people that bitch and whine in their blogs, goading responses from the community at large-- a community of peers that feels the responsibility and honor that comes from being a constituent.<br /><br />Caleb, it is really quite simple. We are not here to please you. Free and Open Source Software is a meritocracy. If you take and take and take, but give nothing back, guess what? You have no standing. We are not going to move mountains for you or spoon feed you bite sized chunks of understanding because you are too fuckin' lazy to read the source. If you do not demonstrate a willingness to be part of our community, then get the fuck out. We have better things to do than coddle leeches.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com3tag:blogger.com,1999:blog-3185710566617725438.post-10894386960297448802010-04-29T14:58:00.000-07:002010-04-29T15:24:10.971-07:00Other languages (specifically .NET languages)Lately with work, I've been busy in some old school .NET 2.0. Until you work with something different, you never really realize how easy it is to do development in Perl. My top three reasons why I enjoy Perl over other languages:<br /><br />1. Built-in complex data structures. I don't need to invoke, import, or compile any additional dependencies to gain lists, arrays, and associative arrays. This is big. Most Perl regulars don't appreciate their hashes and arrays until they are once or twice removed from the core. .NET has these data structures of course, but I have to be "using" the Collections namespace, instanstiate full blown objects, and deal with a lack of literal construct for them. <br /><br />2. Transparency. Much like mst does and recommends, I, too, read the source code of modules before I use them to make sure they are inline with what I could consider good coding practices. This means when I have a problem or a bug with a module, the source to that module is really close at hand. This goes the same for what modules are considered "Core." If I believe there is a problem with how I am using a module, and the documentation is ambiguous, I can always pop open the .pm and figure out wtf is going on. This is much harder to do with the class library with .NET. Sure you can install a reflector and look at generated-from-the-bytecode source, but you lose quite bit of information doing that.<br /><br />3. Platform choice. When we do Perl projects at work, we cover a pretty good spectrum of platforms. Some develop on OSX, I develop on Debian, and we've typically deployed to FreeBSD. Our codebase is typically git friendly, dependencies easily installed with cpanm. With .NET, I pretty much /have/ to develop on windows and deploy on windows and typically use something like svn, or TFS as a source code repository. Mono apologists need not comment. Oh sure you could whip out the cruisecontrol.NET and write your own pull scripts to pull shit out of git (with msysgit), but good luck getting that to fly with any customer that is in love with centralized source repositories and has a vested interest in maintaining them (TFS licenses aren't cheap). So what does that mean for me? It means dealing with the headache of doing development inside of a VM since I do not run windows on my machine for security and performance reasons. <br /><br />Overall I prefer the development of projects in my beloved Perl. It doesn't mean I can't do development in other languages, I have bills to pay after all. It just means that I find the .NET environment to be rather limited and extremely monolithic. And good luck doing anything outside of the status quo, or poking at APIs that are rarely used. Down that path lies dragons.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-63598187881223417772010-04-19T21:53:00.000-07:002010-04-19T21:59:50.961-07:00YAPC::NAThis year is going to be very different.<br /><br />It seems that every talk that was submitted was accepted. I submitted five talks. I'm going to be rather busy this year. <br /><br />Typically, in years, past, a single talk or maybe even two were selected when speakers would submit multiple. Also, there were a limited number of time slots for presentations. This limited the pool of speakers significantly and generally provided a great focus to the event.<br /><br />This year, I worry that there will be an overwhelming amount of material presented. <br /><br />So we will see. Luckily, a couple of my presentations are repeats from previous workshops this year. All in all, besides my concerns, I am extremely excited to see everyone. By far, this is my favorite time of year. Everyone comes out and shows off their awesome projects they've been working on. <br /><br />Anyhow, next week, you'll see an update on Poke! The developer release went smooth, but I still have a lot of work to do in terms of docs and tests.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com2tag:blogger.com,1999:blog-3185710566617725438.post-1776479260342216242010-04-09T11:44:00.000-07:002010-04-09T12:58:06.328-07:00Rewriting POE::Component::Server::PSGISo in following up with last post about TraitFor::Controller::Ping, I've been working on a monitoring framework and daemon. But today's post isn't about that, but about one of the sub components of that project, mainly the embedded HTTP server. <br /><br />I discussed last that I wanted to make POE::Component::Server::PSGI a bit more resource conservative and basically pair it down from the POE::Component::Server::TCP it was using (which creates a new session for each socket connection), down to its constituent parts, mainly SocketFactory, and manage the ReadWrite creation myself. In my monitoring framework I want to display results via a little Web::Simple magic, but I want the app.psgi to take advantage of the frameworks tools to connect to the database and make use of the Schema class. I also wanted to save all of the work from having to open yet another database connection just for the web app. So how do I pass those structures along so the web app can use them?<br /><br />It turns out that POE::Component::Server::PSGI doesn't really provide any means in which to hook into the process to provide any customizations. This is bad if you want to provide an application specific micro-framework to your web app. So I had no choice but to start working on a fork: POEx::Role::PSGIServer<br /><br />First step in the process was to basically start over, but reference frodwith's logic as much as possible. To that end, I started with modern POE tools, mainly, POEx::Role::TCPServer which does exactly what I describe above: a single session managing a collection of wheels (including SocketFactory). <br /><br />The PSGI specification also provides a mechanism for streaming content via a filehandle. And to support that mechanism, I wrote POEx::Role::Streaming, which will take as arguments, two file handles and stream from one to the other. This encapsulates the typical pattern of streaming and since it is a role, it is easy to consume and override with implementation specific details (which is required to support chunked transfer encoding).<br /><br />Then there needed to be a lot more validation of parameters. So POEx::Types::PSGIServer was written to do some light validating on the various data structures passed around inside the role.<br /><br />Lastly, I needed to split out as many of the closures as possible into real live methods so that I could either advise them or override them. Not only that, but also break up the larger pieces of code into smaller bite sized chunks to allow for maximum customization (ie, what to pass when converting the HTTP::Request into PSGI $env hash).<br /><br />All of this is a net win. I am not quite finished yet, but you can take a look at what I have so far at at github: <a href="http://github.com/nperez/poex-role-psgiserver">poex-role-psgiserver</a>. Docs and tests will be written next. I'll probably cargo-cult frodwith's tests as much as possible to save some time. Expect a release very very soon.<br /><br />Anyhow, a BIG thanks to frodwith for his work on POE::Component::Server::PSGI. Next time you see him, buy him an alcoholic beverage of your choice. I know I will.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-72011289374754437052010-03-30T14:05:00.000-07:002010-03-30T14:15:22.472-07:00Catalyst::TraitFor::Controller::PingSo this is a little silly, but I needed a simple method for "pinging" a web app to see if it was up and running. And since for work, this one $client has a whole hell of a lot of apps to monitor, I didn't want to have to script for a bazillion different ways to access these apps. So what is my alternative?<br /><br />How about role for the root controllers? And so Catalyst::TraitFor::Controller::Ping was born.<br /><br />That is my solution. And it takes a couple of different configuration options if you want to do a little more than just spit back an HTTP 200. Give a model name and a method name, and it will attempt to gather that model and execute the method name. If it is successful (meaning, it doesn't cause an exception), then you still get back a 200. Otherwise, the status will be 500 and you get the catalyst error page.<br /><br />I'll talk about the other end next week, but basically I am writing a daemon that takes advantage of this ping role to check statuses, and logs them in a database. And you want access to that information? Well, the third piece is a little Web::Simple magic to write a web service that spits out some JSON. Ideally, I'd like to make POE::Component::Server::PSGI much more resource conservative, and have the daemon+webservice all one thingy, so we will see.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-54779424560106785672010-03-20T21:07:00.000-07:002010-03-20T21:57:14.324-07:00Modules that need loveSo I have been busy with work lately and I haven't had the drive or the time to work on some of my modules.<br /><br />The other day in IRC, someone mentioned that there were problems with POE::Component::PubSub. And I readily admit that there are likely problems with that module. I had already replaced it with POEx::PubSub. But the problem is this: POE::Component::Jabber was scheduled for an overhaul last summer but I was suddenly employed and my work turn a turn toward something else even more awesome. PCJ's dependencies where the first to go through the conversion largely because I needed the new PubSub while working on the POEx::WorkerPool.<br /><br />All that said, I never got around to actually taking the plunge with PCJ and doing the needed conversion to Moose and Roles.<br /><br />So. Some of my modules need some love. POEx::Role::SessionInstantiation specifically was getting some fail reports. That ties into a whole bunch of other modules. Speaking of fail reports, my MooseX::CompileTime::Traits was showing failures on 5.11.x. I need to investigate if I still want my modules to run cleanly on 5.12. POEx::ProxySession particularly could use some real love because I really believe it is the way forward with distributed modern POE versus using old-school IKC. <br /><br />And I really need to follow through on my promises on POE work with specifically taking the time to being Reflex to my will. This would also include organizing talks for the YAPCs this year.<br /><br />Anyhow, with a hopeful lull coming up in work soonish, I might get the time to improve the state of the art. And maybe even take the time to take ownership of that module that mst was pushing some weeks ago. I know I owe him a test for Web::Simple that involved an odd combination for the signature of a particular dispatch.<br /><br />All Perl modules need love. I encourage people that read the Ironman feed to step up and spread some love to your favorite modules. Anything from doc patches, to tests, to feature implementations, no one I know ever turns down work on their projects. In the end, we end up using each other's modules to deliver high-end performing solutions to our clients and partners. Let's make sure we give back.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-35149768733610782252010-03-10T08:13:00.001-08:002010-03-10T09:29:57.524-08:00Of Exquisite Nerd Hackery<span style="font-weight: bold; color: rgb(255, 0, 0);">DISCLAIMER: The following post is for educational purposes only. This post details circumvention of a registration function in a popular flash game. The author does not condone copyright infringement. The author paid for the game and encourages others to support independent developers.</span><br /><br />And so our journey begins. <a href="http://www.rocksolidarcade.com/games/robokill">Robokill</a> is an awesome game. It is like Legend of Zelda with guns. I sunk some time into it last night. Just ask my wife, I was cursing up a storm because one of the levels was being a bitch to finish. In other words, I ♥ Robokill.<br /><br />But then came to the end of the demo. Up popped the REGISTER ME screen in the game asking for an email address. Hrm. Odd. So I plunk down "test@test.com" just to see what would happen. It failed obviously, but Firebug happened to be on and I saw just exactly what kind of request was being sent and the response it was getting. It was just a dumb HTTP GET with the email and a salt in the URL like so:<br /><script src="http://gist.github.com/328034.js?file=gistfile1.txt"></script><br /><br />And the response as just plain text:<br /><pre>test@test.com is not a registered email address! caccabad</pre><br /><br />Wait. What? That's all? The gears started turning at this point.<br /><br />What if I could somehow subvert that request and return a valid result? And what does a valid result even look like? First thing is first, I need to make sure DNS points to somewhere I control.<br /><br />I logged into the local fileserver. Here at home, I am running dnsmasq which is an awesome little utility that provides DHCP services, and local DNS + forwarding. I added an address for www.rocksolidarcade.com and point it back to this machine.<br /><br />Next step was to actually respond to the request. This is when I thought of mst's wonderful <a href="http://search.cpan.org/%7Emstrout/Web-Simple-0.002/lib/Web/Simple.pm">Web::Simple</a>. So my first attempt at returning a result was this:<br /><script src="http://gist.github.com/328057.js?file=gistfile1.PL"></script><br /><br />So how did I run this? I mean it looks like a dumb CGI script. Easy. <a href="http://plackperl.org/">Plack</a>. I simply said:<br /><pre>sudo plackup -p 80 hacks.psgi</pre><br /><br />(I know running on port 80 while sudo is fail, but remember this is a quick hack. A better solution would have been for me to write some iptables rules to send the traffic to a non-privileged port)<br /><br />As you can see, I naively thought that perhaps the server was simply hashing the result and returning it. It would need to be something the client can do too. That failed. So I tried other combinations of things and ultimately wasn't able to make any headway.<br /><br />Then another bright idea came to mind, what if I <span style="font-style: italic;">decompiled</span> the .swf and peered inside the action script to see what it doing? So I downloaded a couple of flash decompilers and installed them in a windows vm. The first one was lame and wouldn't let me see the action script at all without paying (har har). The second one was much more generous though. It let me look but not copy the code. WIN.<br /><br />So take a peak inside and what do I see? Something like this:<br /><script src="http://gist.github.com/328064.js?file=gistfile1.PL"></script><br /><br />Nuh-uh. Really? That dumb?<br /><br />So I adjust my code like so:<br /><script src="http://gist.github.com/328080.js?file=gistfile1.PL"></script><br /><br />And like magic it works.<br /><br />The last step in the process for me is to be able to play the game offline. So I try to load the .swf directly in the browser. So far so good. Even the register check still works. But when I go to press "Start" it wants to popup a window and take me back to their website. Well that is dumb. I want to play it offline.<br /><br />So back into the decompiler I go and I find another tidbit that is explicitly checking the URL for their domain name. Huh. So I adjust my Web::Simple app one last time to search up the file directly:<br /><script src="http://gist.github.com/328029.js?file=gistfile1.PL"></script><br /><br />Now it works whenever I want. But was it really worth my time and effort? No. All in all, it took me about 1.5 hours from start to finish (knowing nothing about Web::Simple, Plack, and futzing with flash decompilers). It would have been much easier to just go get the credit card from the wallet in the other room and pay them the ten bucks first instead of showing off my 1337 skillz. That said, this morning, I did pay them for their wonderful game:<br /><script src="http://gist.github.com/328099.js?file=gistfile1.txt"></script>NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-19834171585881160512010-03-02T08:33:00.000-08:002010-03-02T09:13:57.539-08:00Encouraging ContributorsI think one of the most awesome things about doing open source/free software work is working with others toward a common goal. Of course, when you hold the reins of a projects you can't do everything to please everyone. So the simplest course of action is to encourage people to write failing tests for the functionality they would like to see. <br /><br />This is surprisingly effective for getting regular, active contributors. Of course, once the test is written, and the contributor is confident it tests what they want it to test, it is a simple hop to peering into the code itself and making their own test pass. And usually all that takes is guiding the new found contributor in the right direction: "Oh, your testing for stuff in Flarg.pm, you know, you could probably add that easily if you do X". <br /><br />I think that near-instant gratification is important and it makes herding the cats that much easier. It makes my life that much easier and I am sure I make other people's lives easier when I participate in the same fashion. <br /><br />That said, Perl has a great testing culture developed that makes it very easy to cast the "Write me a test" net, far and wide. Plunk down a new t/foo.t, and fire it off with prove -l. No need to spin up a gigantic harness to make the magic happen. <br /><br />mst is (in)famous for getting work out of people by suggesting projects and ideas to others. Even going as far as private messaging me on IRC about an orphaned module that could use some POEx::SessionInstantiation lovin'. And his method for encouraging contributors is equally as valid and important as well. <br /><br />In the grand scheme of things, we are all trying to make software development easier. Perl, CPAN, its culture of testing, the community at large-- It all just makes it that much better.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-47172688907845086752010-02-20T20:43:00.000-08:002010-02-20T21:04:01.081-08:00Some DBIC::API changes in the pipe and other ranblingsI heart git. I really do. It enables a great development paradigm that allows for easy collaboration for projects with developers all over the world.<br /><br />That said, there is a branch that you should be tracking for DBIC::API, specifically, the object_split branch. Apparently abraxxa needed some functionally that I had mostly written out when developing the 2.x release. He added it back in. And for the most part, it is clean. I still need to push my clean up changes up to the catagits repo, though.<br /><br />Abraxxa also added the functionality to specify a data root for a single object. ExtJS specifically has that part of the Action.Form uncustomizable (which will likely be fixed at some point). That means it expects the result to always be in "data" which doesn't jive for being able to customize the result key for things like restful stores.<br /><br />Also, rafl has been developing a better way to do the dispatching for multiple vs. single objects. Right now it pretty much forwards to objects, and then forwards on to the intended action. This is fail and breaks chaining (as in the case of single objects, and addressed by the object_split branch). Ideally, we'd like to implement a chain that works conditionally. A -> (B || C) -> D -> (E || F). This would allow us to keep the same singular functions in place (update_or_create) that operate on a list of objects, but the prep to get to that point would be dependent either captured args or whatever.<br /><br />It isn't there yet, but we will make it work. We want this module to be as flexible as possible. And we are doing that.<br /><br />DBIC::API is being used, right now, in production, for various applications including ExtJS (which is the biggest driver of our development right now). It should be more than suitable for your basic CRUD tasks. Ideally, we would like to gain more developer support for other systems that speak other dialects than REST and RPC. Your contributions would very much be welcome.<br /><br />We have a lot of momentum at the moment in making this project the best we can. At some point, I'd very much like to push to have it included in various Task projects (Kensho any one?), but that is a little ways away.<br /><br />In other news, it is starting to look like I will be attending YAPC::EU. It isn't final yet, but the finances are in place to execute. This means I need to get the ball rolling on writing abstracts, and doing talk submissions for both YAPC::NA and EU (of course, I am going to NA!).<br /><br />If you have never attended a Perl workshop or conference, I highly recommend that you do. It is where the vast community gathers to share the latest and greatest in ideas. Some are more practical. Some are more theoretical. But all of them good. You can learn a lot about modern Perl practices, talk directly with core developers of key technologies such as Moose, Catalyst, POE, etc., and get a feel for the people that come together to solve these common problems. It is enlightening.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-13256090866508266592010-02-10T22:11:00.000-08:002010-02-10T22:33:04.148-08:00POE and its infinite awesomenessFor the past couple of years, I have been one of the very few to actually mention or discuss POE specific technologies at any of the various Perl conferences or workshops. This makes me sad.<br /><br />POE is a fantastic framework with a very well defined and mature API. As a project it is... wise. It isn't old. It isn't dead. I would argue that POE is one of the longest lived, and thriving Perl projects on CPAN. And in fact, POE handles many mission critical apps on the backends for several companies. You just don't see it.<br /><br />With the advent of newer modules that claim to be the messiah of asynchronous events, POE has developed a sort of marketing problem. POE doesn't release as often because of how mature the framework is. In addition, several other factors contribute to POE not grabbing the spot light. This leads people to believe POE is old school, or that it is too big to be used for their one off projects. I'd like to fix that. I'd like to educate people on why POE is very much still alive and what that means when you have asynchronous requirements for your heavy-lifting backends.<br /><br />That said, this YAPC::NA, I am attempting to organize enough speakers to speak on the topic of POE (Intro, Core, Extentions, Next-Gen POE technologies, Application specific successes) that the conference organizers will have no choice but to give us a significant block of time on one of the tracks. This will involve a lot of work. There is a core group of us meeting in irc://irc.perl.org/#reflex to make this happen. We have a lot of code to write.<br /><br />So please, if you use POE and have an interest in seeing the next evolution of POE work, come join us. We won't bite. Promise.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com4tag:blogger.com,1999:blog-3185710566617725438.post-31408402281622886142010-02-02T21:11:00.000-08:002010-02-02T21:45:18.894-08:00DBIC::API UpdateBe prepared, folks. The next installment of DBIC::API will be backwards compatible <span style="font-weight: bold; font-style: italic;">breaking</span>. This means if you were relying on the old behavior of manipulating the stash for large swaths customization, your customizations will no longer work. The version number will jump significantly in case this isn't warning enough.<br /><br />So far, the Changes will look something like this:<br /><ul><li>Merge create and update into update_or_create</li><li>object is much advanced now:<ul><li>Identifier can be omitted, and data_root in the request is interpreted</li></ul></li><li>Because of the above one object or several is now possible for update or create</li><li>Create and Update object validation now happens iteratively</li><li>Creates and Updates can be mixed inside a single bulk request<br /></li><li>All modifying actions on the database occur within an all-or-nothing transaction</li><li>Much of the DBIC search parameter munging is properly moved to the RequestArguments Role in the form of a trigger on 'search' to populate 'search_parameters' and 'search_attributes' which correspond directly to ->search($parameters, $attributes);<br /></li><li>Error handling is now much more consistent, using Try::Tiny everywhere possible</li><li>Tests are now modernized and use JSON::Any</li><li>Extending is now explicitly done via Moose method modifiers</li><li>The only portion of the stash in use is to allow runtime definition of create/update_allows</li><li>list is now broken down into several steps:<ul><li>list_munge_parameters</li><li>list_perform_search</li><li>list_format_output</li><li>row_format_output (which is just a passthrough per row)</li></ul></li></ul>There will likely be a couple of more bullet points, but as can be plainly seen, this is a <span style="font-weight: bold; font-style: italic;">BIG</span> update. I hope to have the tests and the distribution ready to ship to CPAN tomorrow late in the day (it is still sitting in my local SVK repo)<br /><br />This update will bring DBIC::API to the next level in terms of using it as a web service, with more functionality built into the core by default.<br /><br />If you happen to be attending Frozen Perl 2010, I'll be giving a presentation on Catalyst datagrids, specifically my melding of ExtJS with DBIC::API and how dumb easy it is to hook the two together now (which makes my work life much simpler).<br /><br />Anyhow, if you are still doing lots of heavy, custom CRUD exposed via web service, I hope this update will make it more appealing to switch to DBIC::API to handle the more mundane parts.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com1tag:blogger.com,1999:blog-3185710566617725438.post-33542004924555993342010-01-25T23:20:00.001-08:002010-01-25T23:36:16.918-08:00YAPC::EU and course ideasAfter spending time at OPW2010 with many wonderful people, I was exposed to the thought that YAPC::EU was in fact more better (double comparative), than YAPC::NA. So I decided to look into what exactly it would cost to spend a week in the wonderfully historic Pisa, Italy (where YAPC::EU::2010 will be) in the first week of August.<br /><br />Yikes.<br /><br />Airfare alone is frightening.<br /><br />But I am not going to give up on the idea, at least, not until I consider other possibilities.<br /><br />In IRC tonight, I had the spontaneous idea of perhaps putting together a course with the complete kit of course materials, exercises, etc. My idea of the class goes as such: Modernizing Legacy Perl Applications. Basically, we would explore a pre-modern Perl app (CGI.pm, raw blessed hashes, raw DBI, raw forking, etc) and modernize it over two days (16 hours total). Overall, the class would provide a practical crash course in various aspects of modern Perl (and lots of modules from Task::Kensho) and also a general outline for students to use when implementing their own revitalization project. It would be a tight squeeze for the time allotted, but so far, I haven't seen any other classes take such a tack for introducing modern Perl.<br /><br />While classes that explore Moose and Catalyst at from the beginner and up level, no one seems to put it all together. I'd like to do that. And I'd like for it to be a source of compensation of costs to attend the wonderful YAPCs abroad.<br /><br />Right now the course idea seems like an insurmountable task, but with a couple of interested parties willing to invest time into resources development and teaching, it is very feasible.<br /><br />So we will see. I may see a leaning tower yet :)NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com1tag:blogger.com,1999:blog-3185710566617725438.post-7150184292146269632010-01-16T23:42:00.000-08:002010-01-16T23:50:34.015-08:00OPW2010What an incredibly awesome conference! Kudos to Chris Prather (perigrin) and his lovely wife for organizing this event. It has been a big success in my book and many others. My talk went well and I will publish the slides and code to github soonish.<br /><br />Tomorrow is the hackathon gathering where I hope to dig into updating some bitrotten code of mine. And also perhaps taking a look at metaclass serialization.<br /><br />Short post. More tomorrow perhaps.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-33301808556415930432010-01-07T06:26:00.000-08:002010-01-07T06:53:25.034-08:00More DBIC::APII recently added a new feature to DBIC::API. You can now specify "as" as passed to ->search() that is a complement to the "select" attribute. This doesn't produce a true "as" because the DBIx::Class docs only say it is for internal access, but what it does produce is an accessor of that name (use get_column with specified "as"), to get at columns that are actually results produced from DB functions such as COUNT. This means you can specify:<br /><br /><blockquote>?select.0.count=some_column&amp;as.0=my_count<br /></blockquote><br />using CGI::Expand syntax to produce the right arguments to ->search<br /><br /><blockquote>{ select => [ { count => 'some_column' } ], as => [ 'my_count ] }</blockquote><br /><br />This is rockin.<br /><br />As funky as the CGI::Expand syntax is, it is incredibly flexible. This one minor feature let me avoid a more complicated setup for work and instead rely on what DBIC::API is good for: exposing DBIx::Class via web service.<br /><br />I'll do another release of DBIC::API soon.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-511882181009135882010-01-03T18:31:00.000-08:002010-01-03T18:46:42.097-08:00New Year HackingSo in my last post, I made mention of brokenness in DBIC::API. Specifically, the prefetch_allows only considered hashes with only one key. To my defense, I didn't have any more of a complicated use case. Also, other use cases, in and around the internets and even the docs for DBIC itself, don't include those kinds of complex prefetch. So, I didn't consider that someone might want to prefetch/join on multiple keys like that.<br /><br />Mea culpa.<br /><br />So, my latest commit to the DBIC::API repo, on trunk, makes abraxxa's modified (and initially broken) test pass. First code of the new year! When abraxxa gets back to me, I'll do another release for general consumption.<br /><br />Some other random news: I'll be talking at the Perl Oasis Workshop on workers, job queues, etc and how POEx::WorkerPool is your solution to making things easy. Also, I will be giving a short talk at the Frozen Perl Workshop making use of my recent hacking on DBIC::API and ExtJs for doing datagrids.<br /><br />Before the latter talk, I hope to have some up on CPAN that is a ready-made datagrid that you merely plop in and configure up. I had written about doing a datagrid like that since last year, and ultimately had to delay delivering on that promise until I had made my certain advancements in core technologies. Now that I am getting DBIC::API how I like it and I already have it talking to ExtJs's RESTful Stores, without custom query code.<br /><br />Anyhow, I am excited to get back into the swing of things for work and excited to meet all of the smart Perl peeps you always meet at these kinds of conferences.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com1tag:blogger.com,1999:blog-3185710566617725438.post-50760911442839915222009-12-26T12:49:00.000-08:002009-12-26T12:53:18.206-08:00DBIC::APIWith work deadlines, holiday baking, and playing in the snow (in Texas no less), I got sidetracked and never wrote the advent article I promised for the Catalyst folks. I feel horrible.<br /><br />As if that wasn't bad enough, apparently there are more complex configurations out there that I didn't account for in my DBIC::API branch (that is now merged in and is trunk), so it breaks people's apps.<br /><br />So I know what I will be doing next week: fixing the brokenness.<br /><br />Short post. See you next year.NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0tag:blogger.com,1999:blog-3185710566617725438.post-49690460597368664242009-12-18T02:12:00.001-08:002009-12-18T02:12:47.356-08:00Of more nerdhackeryI admit I am a dork. So I recently started playing Magic: The Gathering again with real-live cards. And it is rather a pain in the ass to figure what I need to build a bad ass deck with out shelling out for some stupid guide that lists all of the cards in a set.<br /><br />Luckily, someone was awesome enough to type up all of the cards in various sets (including the activation text, if applicable). But you can tell the goofballs manage this data as a flatfile of text. It is very regular though. If only that were a database...<br /><br />So step one is to build a schema using DBIx::Class that represents the various fields present in the file (with some of them broken down further so you could separate things like power and toughness instead of being a single field). Next, write the line-by-line parser using simple regex. Convert parsed data into created rows. And like magic, we have a database.<br /><br />So what can we now do with this database beyond the boring crap like searching for cards by casting cost? Write a deck analyzer of course!<br /><br />I haven't executed this part yet. But I will. And it will be most excellent. See, I figure I can take this data and throw rules engine at it. Provide a Cat app for building decks and running it through the rules engine to determine things like "Not enough land to support this deck."<br /><br />Doing a quick search on CPAN returns a number of rules engines to use, but I think FSA::Rules shows the most promise. It is simple enough to not get in the way, and lets you define your own internal data.<br /><br />The Cat app could obviously do more like build out dumb starter decks based some rules too. So far I haven't found any thing on the internets that does what I am wanting to build. This is good.<br /><br />Hooray nerdhackery!NPEREZhttp://www.blogger.com/profile/07774396754686720265noreply@blogger.com0