Re: Routing and controller arguments in K3
Shad Laws <shad-xpYdmXCiSuZWk0Htik3J/[email protected]> Mon, 15 Apr 2013 18:18:54 +0200
| Newsgroups | gmane.comp.web.gallery.devel |
|---|---|
| Message-ID | <CA+z51A6O1Y_T7FFEYVw2fTfRFmgms5MvyLbeQhWpawtDgHT6eA@mail.gmail.com> |
--===============2072419747076054304==
Content-Type: multipart/alternative; boundary=089e014942261e2ba504da689d7c
--089e014942261e2ba504da689d7c
Content-Type: text/plain; charset=UTF-8
Hey Bharat,
The good news is that the whole isset/default capability is already baked
into K3's Request::param(). So:
$this->request->param("undefined_foo") --> null
$this->request->param("undefined_foo", "default") --> "default"
$this->request->param("defined_foo") --> "bar"
Or, from a non-Controller context:
Request::current()->param("undefined_foo") --> null
Request::current()->param("undefined_foo", "default") --> "default"
Request::current()->param("defined_foo") --> "bar"
I think this is the primary reason why reverse routing (using Route::uri())
works better - there's no error thrown if someone asks for an argument that
doesn't exist.
The biggest gotcha is that you need to define the parameter names in the
routes at bootstrap (or module init). So, to ensure that
Admin_Themes::action_preview() got parameters called "type" and
"theme_name," we'd have to define a new route specifically for
"/<directory>/<controller>/<action>/<type>/<theme_name>" (where directory,
controller, and action are restricted to admin, themes, and preview,
respectively). And if we really want to follow this philosophy carefully,
we'd need to filter out ~80 actions and ensure their parameters are defined
correctly (the remaining ~80 don't need parameters).
It seems like we're roughly on the same page that this isn't an ideal
approach, especially for a user-extensible application like Gallery. It
favors configuration over convention in kind of annoying way. I've read
through the issue thread you found and poked around the codebase, and it
doesn't *seem* like generic argument names fundamentally break reverse
routing (but I could be corrected). That said, it does *suggest* that
generic argument names are kinda uncool. And, it seems to indicate that
many people like us disagree with this decision.
So, having thought about it a bit more, here's my new proposal:
First, revise the routes in bootstrap. They'd start off with something
like this:
Route::set("example", "<controller>(/<action>(/<args>))",
array("args" => "[^.,;?\n]++")
The regex for args is the same as Route::REGEX_SEGMENT except that it
allows slashes.
Next, add some new functionality to our Controller override. While we're
at it, we should do some XSS cleaning (if stuff after ? gets cleaned via
$_GET, it seems like stuff before should be, too):
class Gallery_Controller extends Kohana_Controller {
protected $_args = null;
public function args($index, $default=null) {
if ($this->_args === null) {
$this->_args = trim($this->request->param("args"), "/");
$this->_args = explode("/", (array) $this->_args);
$this->_args = Purifier::clean_html($this->_args);
}
return Arr::get($this->_args, $index, $default);
}
... other non-args stuff ...
}
The cleaning shouldn't be too expensive, as passing an already-initialized
purifier a string of "1" should be relatively quick.
Then, revise the actions to use it. For the Admin_Themes::action_preview()
example:
function action_preview() {
$type = $this->args(0);
$theme_name = $this->args(1);
$i_do_not_exist = $this->args(2);
$i_default_to_foo = $this->args(3, "foo");
...
}
It seems like the biggest downside to this approach is that, if you want a
reverse route, you need to remember the argument order (e.g. "type" goes
before "theme_name" in "args"). Furthermore, changing the argument order
breaks the reverse routes. But, the alternative is configuring the routes
ahead of time, adding an extra place to define things... which I,
personally, feel worse about.
Thoughts?
Take care,
Shad
On 14 April 2013 07:15, Bharat Mediratta <[email protected]> wrote:
>
> Finally getting around to this! I'm sad to see the controller action
> function args go away - they were very convenient. I wonder why they were
> removed?
>
> Either way - I think that $this->request->param("...") is pretty unwieldy.
> I was looking to see whether we should reinstate this functionality and I
> found http://dev.kohanaframework.org/issues/3536 which tells the back
> story.
>
> In essence, they're saying that generic arguments are a bad idea because
> they break reverse routing. Instead, every argument should be a named
> argument. This also allows for reverse routing, which I think will be
> essential for our embedding story. So for the most part, this is easy
> because we're generally dealing with $id as our argument. But it means
> that we shouldn't use $args as a generic arg holder, but we should stick
> with names.
>
> So Admin_Themes in K2 has:
>
> function preview($type, $theme_name) {
> ...
> }
>
> and that would become this in K3:
>
> function action_preview() {
> $type = $this->request->param("type");
> $theme_name = $this->request->param($theme_name);
> ...
> }
>
> One interesting thing to note here is that in K2, both of those args are
> required so if you leave off one of them it throws an error. In K3, I
> don't see a facility for required params - have you seen anything about
> that? Without that, we'll need to check for missing params all the time..
> ugh. We need a shorthand for this *and* we need an easy way to define a
> param as mandatory.
>
> Proposal:
> Create a base class controller function that provides two members: param
> and optional_param. The API would look like this:
>
> function action_preview() {
> $type = $this->param->type; *// throw exception if type is not
> defined*
> $theme_name = $this->param->theme_name;
> $some_value = $this->optional_param->some_value;
> *// return null if some_value is not defined*
> ...
> }
>
> The code would look like this:
>
> class Controller_Params {
> public function __construct($params, $mandatory=true) {
> $this->_params = $params;
> }
>
> function __get($key) {
> if (!isset($this->_params[$key])) {
> if ($mandatory) {
> throw new Exception("Missing argument for $key");
> }
> return null;
> }
> return $this->_params[$key];
> }
> }
>
> Then in the Gallery_Controller base class (or the default before() or
> whatever we do across all controllers):
>
> public function __construct() {
> $this->params = new Controller_Params($this->request->params);
> $this->optional_params = new Controller_Params($this->request->params);
> }
>
> This is just a straw man. Please shred it :-)
>
>
> On Wed, Apr 10, 2013 at 3:32 PM, Shad Laws <shad-xpYdmXCiSuZWk0Htik3J/[email protected]> wrote:
>
>> Hey gang,
>>
>> So, calling controller actions with arguments was carried over to 3.0,
>> deprecated in 3.1, and removed in 3.2.
>>
>> Example: we want index.php/bar/foo/123. What used to be this:
>>
>> public function foo($id=null) {
>> // Do something with $id = 123...
>> }
>>
>> Will now need to be something like this:
>>
>> public function action_foo() {
>> $id = $this->request->param("id", null);
>> // Do something with $id = 123...
>> }
>>
>> With a route defined like this:
>>
>> Route::set("example", "(<controller>(/<action>(/<id>)))")
>>
>> A quick survey of Gallery's controllers (in the core repo) shows that,
>> in 162 controller actions, we have:
>> - 81 with 0 args
>> - 70 with 1 arg (typically named something like $id)
>> - 9 with 2 args
>> - 1 with 3 args
>> - 1 with 4 args
>> - 0 with 5+ args
>>
>> So, here's my proposal: we optimize ourselves for 0-1 arguments, and
>> make a flexible system to handle 2+. We use a route something like
>> this:
>>
>> Route::set("example", "(<controller>(/<action>(/<id>(/<args>))))")
>> ->filter( // Use an explode for "/" to parse args into an array here )
>>
>> And then access them like this:
>>
>> $id = $this->request->param("id");
>> $args = $this->request->param("args");
>> $second = $args[0];
>> $third = $args[1];
>> $fourth = $args[2];
>> $fifth = $args[3];
>> ...
>> $fiftieth = $arg[48];
>>
>> This keeps our typical cases simple and lean, while being totally
>> flexible for our other 11 cases as well as anything a contrib module
>> could dream up. Does this seem like a reasonable approach?
>>
>> Take care,
>> Shad
>>
>>
>> ------------------------------------------------------------------------------
>> Precog is a next-generation analytics platform capable of advanced
>> analytics on semi-structured data. The platform includes APIs for building
>> apps and a phenomenal toolset for data science. Developers can use
>> our toolset for easy data analysis & visualization. Get a free account!
>> http://www2.precog.com/precogplatform/slashdotnewsletter
>> __[ g a l l e r y - d e v e l ]_________________________
>>
>> [ list info/archive --> http://gallery.sf.net/lists.php ]
>> [ gallery info/FAQ/download --> http://gallery.sf.net ]
>>
>>
>
--089e014942261e2ba504da689d7c
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
Hey Bharat,<div><br></div><div>The good news is that the whole isset/defaul=
t capability is already baked into K3's Request::param(). =C2=A0So:</di=
v><div>$this->request->param("undefined_foo") --> null</=
div>
<div><div>$this->request->param("undefined_foo", "defa=
ult") --> "default"</div><div><div>$this->request->=
param("defined_foo") --> "bar"</div></div><div>
<br>
</div><div>Or, from a non-Controller context:</div><div><div>Request::curre=
nt()->param("undefined_foo") --> null</div><div><div>Reques=
t::current()->param("undefined_foo", "default") --&g=
t; "default"</div>
<div>Request::current()->param("defined_foo") --> "bar=
"</div></div></div><div><br></div><div>I think this is the primary rea=
son why reverse routing (using Route::uri()) works better - there's no =
error thrown if someone asks for an argument that doesn't exist.</div>
<div><br></div><div>The biggest gotcha is that you need to define the param=
eter names in the routes at bootstrap (or module init). =C2=A0So, to ensure=
that Admin_Themes::action_preview() got parameters called "type"=
and "theme_name," we'd have to define a new route specifical=
ly for "/<directory>/<controller>/<action>/<type&=
gt;/<theme_name>" (where directory, controller, and action are r=
estricted to admin, themes, and preview, respectively). =C2=A0And if we rea=
lly want to follow this philosophy carefully, we'd need to filter out ~=
80 actions and ensure their parameters are defined correctly (the remaining=
~80 don't need parameters).</div>
<div><br></div><div>It seems like we're roughly on the same page that t=
his isn't an ideal approach, especially for a user-extensible applicati=
on like Gallery. =C2=A0It favors configuration over convention in kind of a=
nnoying way. =C2=A0I've read through the issue thread you found and pok=
ed around the codebase, and it doesn't *seem* like generic argument nam=
es fundamentally break reverse routing (but I could be corrected). =C2=A0Th=
at said, it does *suggest* that generic argument names are kinda uncool. =
=C2=A0And, it seems to indicate that many people like us disagree with this=
decision.</div>
<div><br></div><div>So, having thought about it a bit more, here's my n=
ew proposal:</div><div><br></div><div>First, revise the routes in bootstrap=
. =C2=A0They'd start off with something like this:</div><div><br></div>
<div>
<div><font face=3D"courier new, monospace">Route::set("example", =
"<controller>(/<action>(/<args>))",</font></div=
><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0array("args" =3D> "[^.,;?\n]++")</font></d=
iv>
<div><br></div></div><div>The regex for args is the same as Route::REGEX_SE=
GMENT except that it allows slashes.</div><div><br></div><div>Next, add som=
e new functionality to our Controller override. =C2=A0While we're at it=
, we should do some XSS cleaning (if stuff after ? gets cleaned via $_GET, =
it seems like stuff before should be, too):</div>
<div><br></div><div><font face=3D"courier new, monospace">class Gallery_Con=
troller extends Kohana_Controller {</font></div><div><font face=3D"courier =
new, monospace">=C2=A0 protected $_args =3D null;</font></div><div><font fa=
ce=3D"courier new, monospace"><br>
</font></div><div><span style=3D"font-family:'courier new',monospac=
e">=C2=A0 public function args($index, $default=3Dnull) {</span></div><div>=
<font face=3D"courier new, monospace">=C2=A0 =C2=A0 if ($this->_args =3D=
=3D=3D null) {</font></div>
<div><span style=3D"font-family:'courier new',monospace">=C2=A0 =C2=
=A0 =C2=A0 $this->_args =3D trim($this->request->param("args&=
quot;), "/");</span></div><div><span style=3D"font-family:'co=
urier new',monospace">=C2=A0 =C2=A0 =C2=A0 $this->_args =3D explode(=
"/",=C2=A0</span><span style=3D"font-family:'courier new'=
,monospace">(array)=C2=A0</span><span style=3D"font-family:'courier new=
',monospace">$this->_args</span><span style=3D"font-family:'cour=
ier new',monospace">);</span></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 $this->_=
args =3D=C2=A0</font><span style=3D"font-family:'courier new',monos=
pace">Purifier::clean_html(</span><span style=3D"font-family:'courier n=
ew',monospace">$this->_args</span><span style=3D"font-family:'co=
urier new',monospace">);</span></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 }</font></div><div=
><font face=3D"courier new, monospace">=C2=A0 =C2=A0 return Arr::get($this-=
>_args, $index, $default);</font></div><div><font face=3D"courier new, m=
onospace">=C2=A0 }</font></div>
<div><font face=3D"courier new, monospace"><br></font></div><div><font face=
=3D"courier new, monospace">=C2=A0 ... other non-args stuff ...</font></div=
><div><font face=3D"courier new, monospace">}</font></div><div><br></div><d=
iv>The cleaning shouldn't be too expensive, as passing an already-initi=
alized purifier a string of "1" should be relatively quick.</div>
<div><br></div><div>Then, revise the actions to use it. =C2=A0For the Admin=
_Themes::action_preview() example:</div><div><br></div><div><div><font face=
=3D"courier new, monospace">=C2=A0 function action_preview() {</font></div>=
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 $type =3D $this-&g=
t;args(0);</font></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 $theme_name =3D $t=
his->args(1);</font></div><div><font face=3D"courier new, monospace"><br=
></font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 $i_d=
o_not_exist =3D $this->args(2);</font></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 $i_default_to_foo =
=3D $this->args(3, "foo");</font></div><div><font face=3D"cour=
ier new, monospace">=C2=A0 =C2=A0 ...</font></div><div><font face=3D"courie=
r new, monospace">=C2=A0 }</font></div>
</div><div><font face=3D"courier new, monospace"><br></font></div><div>It s=
eems like the biggest downside to this approach is that, if you want a reve=
rse route, you need to remember the argument order (e.g. "type" g=
oes before "theme_name" in "args"). =C2=A0Furthermore, =
changing the argument order breaks the reverse routes. =C2=A0But, the alter=
native is configuring the routes ahead of time, adding an extra place to de=
fine things... which I, personally, feel worse about.</div>
<div><br></div><div>Thoughts?</div><div><br></div><div>Take care,</div><div=
>Shad</div><div><br></div><div><br></div><div class=3D"gmail_quote">On 14 A=
pril 2013 07:15, Bharat Mediratta <span dir=3D"ltr"><<a href=3D"mailto:b=
[email protected]" target=3D"_blank">[email protected]</a>></span> wrot=
e:<br>
<blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;border-left:1p=
x #ccc solid;padding-left:1ex"><div dir=3D"ltr"><br><div>Finally getting ar=
ound to this! =C2=A0I'm sad to see the controller action function args =
go away - they were very convenient. =C2=A0I wonder why they were removed?<=
/div>
<div><br></div><div>
Either way - I think that $this->request->param("...") is p=
retty unwieldy. =C2=A0I was looking to see whether we should reinstate this=
functionality and I found=C2=A0<a href=3D"http://dev.kohanaframework.org/i=
ssues/3536" target=3D"_blank">http://dev.kohanaframework.org/issues/3536</a=
> which tells the back story.</div>
<div><br></div><div>In essence, they're saying that generic arguments a=
re a bad idea because they break reverse routing. =C2=A0Instead, every argu=
ment should be a named argument. =C2=A0This also allows for reverse routing=
, which I think will be essential for our embedding story. =C2=A0So for the=
most part, this is easy because we're generally dealing with $id as ou=
r argument. =C2=A0But it means that we shouldn't use $args as a generic=
arg holder, but we should stick with names.</div>
<div><br></div><div>So Admin_Themes in K2 has:</div><div><br></div><div><fo=
nt face=3D"courier new, monospace">=C2=A0 function preview($type, $theme_na=
me) {</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =
...</font></div>
<div><font face=3D"courier new, monospace">=C2=A0 }</font></div><div><br></=
div><div>and that would become this in K3:</div><div><br></div><div><font f=
ace=3D"courier new, monospace">=C2=A0 function action_preview() {</font></d=
iv>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 $type =3D $this-&g=
t;request->param("type");</font></div><div><font face=3D"couri=
er new, monospace">=C2=A0 =C2=A0 $theme_name =3D $this->request->para=
m($theme_name);</font></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 ...</font></div><d=
iv><font face=3D"courier new, monospace">=C2=A0 }</font></div><div><br></di=
v><div>One interesting thing to note here is that in K2, both of those args=
are required so if you leave off one of them it throws an error. =C2=A0In =
K3, I don't see a facility for required params - have you seen anything=
about that? =C2=A0Without that, we'll need to check for missing params=
all the time.. ugh. =C2=A0We need a shorthand for this <b>and</b>=C2=A0we =
need an easy way to define a param as mandatory.</div>
<div><br></div><div>Proposal:</div><div>Create a base class controller func=
tion that provides two members: param and optional_param. The API would loo=
k like this:</div><div><br></div><div><div>
<font face=3D"courier new, monospace">=C2=A0 function action_preview() {</f=
ont></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 $type =
=3D $this->param->type; =C2=A0 <b>// throw exception if <i>type</i>=
=C2=A0is not defined</b></font></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 $theme_name =3D $t=
his->param->theme_name;</font></div><div><div><font face=3D"courier n=
ew, monospace">=C2=A0 =C2=A0 $some_value =3D $this->optional_param->s=
ome_value;</font></div>
<div>
<font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0=C2=A0<b>// retur=
n null if some_value is not defined</b></font></div></div><div><font face=
=3D"courier new, monospace">=C2=A0 =C2=A0 ...</font></div><div><font face=
=3D"courier new, monospace">=C2=A0 }</font></div>
<div><br></div><div>The code would look like this:</div><div><br></div><div=
><font face=3D"courier new, monospace">=C2=A0 class Controller_Params {</fo=
nt></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 public fu=
nction __construct($params, $mandatory=3Dtrue) {</font></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 $this->_=
params =3D $params;</font></div><div><font face=3D"courier new, monospace">=
=C2=A0 =C2=A0 }</font></div><div><font face=3D"courier new, monospace"><br>=
</font></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 function __get($ke=
y) {</font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =
=C2=A0 if (!isset($this->_params[$key])) {</font></div><div><font face=
=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 if ($mandatory) {</=
font></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 throw new Exception("Missing argument for $key");</font></div=
><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =C2=A0 }</=
font></div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 return null;</font></div>
<div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 }</font></d=
iv><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 =C2=A0 return $=
this->_params[$key];</font></div><div><font face=3D"courier new, monospa=
ce">=C2=A0 =C2=A0 }</font></div>
<div><font face=3D"courier new, monospace">=C2=A0 }</font></div><div><br></=
div><div>Then in the Gallery_Controller base class (or the default before()=
or whatever we do across all controllers):</div><div>
<br></div><div><font face=3D"courier new, monospace">=C2=A0 public function=
__construct() {</font></div><div><font face=3D"courier new, monospace">=C2=
=A0 =C2=A0 $this->params =3D new Controller_Params($this->request->=
;params);</font></div>
<div><div><font face=3D"courier new, monospace">=C2=A0 =C2=A0 $this->opt=
ional_params =3D new Controller_Params($this->request->params);</font=
></div><div><font face=3D"courier new, monospace">=C2=A0 }</font></div><div=
><font face=3D"courier new, monospace"><br>
</font></div><div><font face=3D"arial, helvetica, sans-serif">This is just =
a straw man. =C2=A0Please shred it :-)</font></div></div></div></div><div c=
lass=3D"gmail_extra"><br><br><div class=3D"gmail_quote">On Wed, Apr 10, 201=
3 at 3:32 PM, Shad Laws <span dir=3D"ltr"><<a href=3D"mailto:shad@shadla=
ws.com" target=3D"_blank">shad-xpYdmXCiSuZWk0Htik3J/[email protected]</a>></span> wrote:<br>
<blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;border-left:1p=
x #ccc solid;padding-left:1ex">Hey gang,<br>
<br>
So, calling controller actions with arguments was carried over to 3.0,<br>
deprecated in 3.1, and removed in 3.2.<br>
<br>
Example: we want index.php/bar/foo/123. =C2=A0What used to be this:<br>
<br>
public function foo($id=3Dnull) {<br>
=C2=A0 // Do something with $id =3D 123...<br>
}<br>
<br>
Will now need to be something like this:<br>
<br>
public function action_foo() {<br>
=C2=A0 $id =3D $this->request->param("id", null);<br>
=C2=A0 // Do something with $id =3D 123...<br>
}<br>
<br>
With a route defined like this:<br>
<br>
Route::set("example", "(<controller>(/<action>(/=
<id>)))")<br>
<br>
A quick survey of Gallery's controllers (in the core repo) shows that,<=
br>
in 162 controller actions, we have:<br>
- 81 with 0 args<br>
- 70 with 1 arg (typically named something like $id)<br>
- 9 with 2 args<br>
- 1 with 3 args<br>
- 1 with 4 args<br>
- 0 with 5+ args<br>
<br>
So, here's my proposal: we optimize ourselves for 0-1 arguments, and<br=
>
make a flexible system to handle 2+. =C2=A0We use a route something like<br=
>
this:<br>
<br>
Route::set("example", "(<controller>(/<action>(/=
<id>(/<args>))))")<br>
=C2=A0 ->filter( // Use an explode for "/" to parse args into =
an array here )<br>
<br>
And then access them like this:<br>
<br>
$id =3D $this->request->param("id");<br>
$args =3D $this->request->param("args");<br>
$second =3D $args[0];<br>
$third =3D $args[1];<br>
$fourth =3D $args[2];<br>
$fifth =3D $args[3];<br>
...<br>
$fiftieth =3D $arg[48];<br>
<br>
This keeps our typical cases simple and lean, while being totally<br>
flexible for our other 11 cases as well as anything a contrib module<br>
could dream up. =C2=A0Does this seem like a reasonable approach?<br>
<br>
Take care,<br>
Shad<br>
<br>
---------------------------------------------------------------------------=
---<br>
Precog is a next-generation analytics platform capable of advanced<br>
analytics on semi-structured data. The platform includes APIs for building<=
br>
apps and a phenomenal toolset for data science. Developers can use<br>
our toolset for easy data analysis & visualization. Get a free account!=
<br>
<a href=3D"http://www2.precog.com/precogplatform/slashdotnewsletter" target=
=3D"_blank">http://www2.precog.com/precogplatform/slashdotnewsletter</a><br=
>
__[ g a l l e r y - d e v e l ]_________________________<br>
<br>
[ list info/archive --> <a href=3D"http://gallery.sf.net/lists.php" targ=
et=3D"_blank">http://gallery.sf.net/lists.php</a> ]<br>
[ gallery info/FAQ/download --> <a href=3D"http://gallery.sf.net" target=
=3D"_blank">http://gallery.sf.net</a> ]<br>
<br>
</blockquote></div><br></div>
</blockquote></div><br></div>
--089e014942261e2ba504da689d7c--
--===============2072419747076054304==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
------------------------------------------------------------------------------
Precog is a next-generation analytics platform capable of advanced
analytics on semi-structured data. The platform includes APIs for building
apps and a phenomenal toolset for data science. Developers can use
our toolset for easy data analysis & visualization. Get a free account!
http://www2.precog.com/precogplatform/slashdotnewsletter
--===============2072419747076054304==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
__[ g a l l e r y - d e v e l ]_________________________
[ list info/archive --> http://gallery.sf.net/lists.php ]
[ gallery info/FAQ/download --> http://gallery.sf.net ]
--===============2072419747076054304==--