summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/vagrant-ansible-ec2.webpbin335548 -> 0 bytes
-rw-r--r--posts/2009-10-03-teh-batmoose-at-osdc.fr.md2
-rw-r--r--posts/2010-09-17-spore.md116
-rw-r--r--posts/2010-10-20-spore-update.md336
-rw-r--r--posts/2014-02-15-cursive-nrepl-cljs-oh-my.md39
5 files changed, 239 insertions, 254 deletions
diff --git a/assets/vagrant-ansible-ec2.webp b/assets/vagrant-ansible-ec2.webp
deleted file mode 100644
index 55572ae..0000000
--- a/assets/vagrant-ansible-ec2.webp
+++ /dev/null
Binary files differ
diff --git a/posts/2009-10-03-teh-batmoose-at-osdc.fr.md b/posts/2009-10-03-teh-batmoose-at-osdc.fr.md
index 35b13bb..3717b85 100644
--- a/posts/2009-10-03-teh-batmoose-at-osdc.fr.md
+++ b/posts/2009-10-03-teh-batmoose-at-osdc.fr.md
@@ -1,3 +1,5 @@
Today I presented a talk about Moose at [OSDC.fr](http://osdc.fr). The slides are available [here](http://franck.lumberjaph.net/blog/slides/Introduction_a_Moose.pdf)
And big thanks to my friend [Morgan](http://www.bwoup.com) for his illustration of the batmoose :)
+
+![batmoose](../assets/batmoose_1024cut-300x225.webp)
diff --git a/posts/2010-09-17-spore.md b/posts/2010-09-17-spore.md
index a9becc9..47513d0 100644
--- a/posts/2010-09-17-spore.md
+++ b/posts/2010-09-17-spore.md
@@ -1,5 +1,4 @@
-Specification to a POrtable Rest Environment
---------------------------------------------
+# Specification to a POrtable Rest Environment
More and more web services offer [ReST API](http://en.wikipedia.org/wiki/Representational_State_Transfer). Every time you want to access one of these services, you need to write a client for this API, or find an existing one. Sometimes, you will find the right library that will do exactly what you need. Sometimes you won't, and you end up writing your own.
@@ -15,8 +14,7 @@ With SPORE, I propose a better solution for this. SPORE is composed of two parts
- a specification that describes an API
- a "framework" for clients (think this as [WSGI](http://www.python.org/dev/peps/pep-0333/), [Plack](http://plackperl.org/), [Rack](http://rack.rubyforge.org/), [JSGI](http://jackjs.org/jsgi-spec.html), ...)
-API Specifications
-------------------
+## API Specifications
I know, at this point, you're thinking "what ? isn't it just what [WSDL](http://en.wikipedia.org/wiki/Web_Services_Description_Language) does ?". Well, yes. But it's (in my opinion) simpler to write this than to write a WSDL. And when you say "WSDL" people think "[SOAP](http://en.wikipedia.org/wiki/SOAP_(protocol))", and that's definitly not a good thing.
@@ -28,15 +26,15 @@ The first part is the specification to API. A ReST request is mainly :
This is **easy to describe**. For this example, I will use the [twitter API](http://dev.twitter.com/doc/get/statuses/public_timeline). So, if i want to describe the user timeline method, we will get something like this:
-``` example
- public_timeline:
- method: GET
- path: /statuses/public_timeline.:format
- params:
- - trim_user
- - include_entities
- required:
- - format
+``` yaml
+public_timeline:
+ method: GET
+ path: /statuses/public_timeline.:format
+ params:
+ - trim_user
+ - include_entities
+ required:
+ - format
```
Whatever your language of choice is, you'll always need this informations. The idea with the API description, is that it can be reused by every language. If the API provider publishes this file, everyone can easily use it. It's very similar to a documentation (for the twitter description, all I needed to do was to copy/paste the documentation, it's really that simple) but it can be used by a framework to generate a client.
@@ -50,8 +48,7 @@ There is many advantages to do it this way:
I've started to write some specifications for a few services ([twitter](https://github.com/SPORE/api-description/blob/master/services/twitter.json), [github](https://github.com/SPORE/api-description/blob/master/services/github.json), [backtype](https://github.com/franckcuny/spore/blob/master/services/backtype.json), [backtweet](https://github.com/franckcuny/spore/blob/master/services/backtweet.json), ...) and applications ([couchdb](https://github.com/franckcuny/spore/blob/master/apps/couchdb.json), [presque](https://github.com/franckcuny/spore/blob/master/apps/presque.json)). They are not complete yet, so you're welcomed to [fork the repository](https://github.com/franckcuny/spore), add missings methods, and add your own specifications! :)
-Client Specification
---------------------
+## Client Specification
Now that we have a simple description for the API, we want to have [an easy solution to use it](https://github.com/franckcuny/net-http-spore/blob/master/spec/spore_implementation.pod). I will describe [the Perl implementation](https://github.com/franckcuny/net-http-spore), but there is also one for Ruby (will be published soon), and a early version for [Clojure](http://github.com/ngrunwald/clj-spore) and [Python](http://github.com/elishowk/pyspore).
@@ -70,14 +67,14 @@ The core will create the appropriate functions using the previous description. T
For each method declared in the description, I build a new Moose method I will attach to my class. Basicaly, the code looks like this:
``` perl
- foreach my $method_name ( keys %$methods_spec ) {
- $class->meta->add_spore_method(
- "user_timeline",
- path => '/statuses/public_timeline.:format',
- required => [qw/format/],
- params => [qw/trim_user include_entities/]
- );
- }
+foreach my $method_name ( keys %$methods_spec ) {
+ $class->meta->add_spore_method(
+ "user_timeline",
+ path => '/statuses/public_timeline.:format',
+ required => [qw/format/],
+ params => [qw/trim_user include_entities/]
+ );
+}
```
The code of the `user_timelime` method will be generated via the `add_spore_method`.
@@ -91,36 +88,38 @@ Middlewares are the nice part of it. By default, the core only creates a request
- proxying
- ...
+![char](../assets/chart.webp)
+
The most obvious middleware is the one that handles the format. When you load the middleware Format::JSON, it will set various headers on your request. In case of a GET method, the **Accept** header will be set to **application/json**. For a POST, the **Content-Type** will be also set. Before returning the result to the client, the content will be transformed from JSON to a Perl structure.
For twitter, I can have a client with this few lines:
``` perl
- my $client = Net::HTTP::Spore->new_from_spec('twitter.json');
- $client->enable('Format::JSON');
-
- my $timeline = $client->public_timeline( format => 'json', include_rts => 1 );
- my $tweets = $timeline->body;
- foreach my $tweet (@$tweets) {
- say $tweet->{user}->{screen_name} . " says " . encode_utf8($tweet->{text});
- }
+my $client = Net::HTTP::Spore->new_from_spec('twitter.json');
+$client->enable('Format::JSON');
+
+my $timeline = $client->public_timeline( format => 'json', include_rts => 1 );
+my $tweets = $timeline->body;
+foreach my $tweet (@$tweets) {
+ say $tweet->{user}->{screen_name} . " says " . encode_utf8($tweet->{text});
+}
```
Now, I want to use my friends timeline, which requires OAuth ? easy
``` perl
- $client->enable(
- 'Auth::OAuth',
- consumer_key => $consumer_key,
- consumer_secret => $consumer_secret,
- token => $token,
- token_secret => $token_secret,
- );
- my $friends_timeline = $client->friends_timeline(format => 'json', include_rts => 1);
- my $tweets = $friends_timeline->body;
- foreach my $tweet (@$tweets) {
- print $tweet->{user}->{screen_name} . " says " . encode_utf8($tweet->{text}) . "\n";
- }
+$client->enable(
+ 'Auth::OAuth',
+ consumer_key => $consumer_key,
+ consumer_secret => $consumer_secret,
+ token => $token,
+ token_secret => $token_secret,
+);
+my $friends_timeline = $client->friends_timeline(format => 'json', include_rts => 1);
+my $tweets = $friends_timeline->body;
+foreach my $tweet (@$tweets) {
+ print $tweet->{user}->{screen_name} . " says " . encode_utf8($tweet->{text}) . "\n";
+}
```
Middlewares are easy to write. They should implement a *call* method, which receive a request object as argument. The middleware can return:
@@ -132,26 +131,25 @@ Middlewares are easy to write. They should implement a *call* method, which rece
A simple middleware that add a runtime header to the response object will look like this:
``` perl
- sub call {
- my ( $self, $req ) = @_;
-
- my $start_time = [Time::HiRes::gettimeofday];
-
- $self->response_cb(
- sub {
- my $res = shift;
- my $req_time = sprintf '%.6f',
- Time::HiRes::tv_interval($start_time);
- $res->header( 'X-Spore-Runtime' => $req_time );
- }
- );
- }
+sub call {
+ my ( $self, $req ) = @_;
+
+ my $start_time = [Time::HiRes::gettimeofday];
+
+ $self->response_cb(
+ sub {
+ my $res = shift;
+ my $req_time = sprintf '%.6f',
+ Time::HiRes::tv_interval($start_time);
+ $res->header( 'X-Spore-Runtime' => $req_time );
+ }
+ );
+}
```
I've tried to mimic as much as possible Plack's behavior. The result of a request is a Response object, but you can also use it as an arrayref, with the following values \[http\_code, \[http\_headers\], body\].
-Conclusion
-----------
+## Conclusion
The real target for this are not API developers (even if it's useful to have this when you write your own API, I will show some examples soon), neither client developers (even it's really easier to do with this), but people who want to play immediatly with an API to fetch data, without the coding skill or knowledge of what an HTTP request is, how to define headers, what is OAuth, ... As it was suggested to me, an implementation for [R](http://en.wikipedia.org/wiki/R_(programming_language)) would be really usefull to a lot of people.
diff --git a/posts/2010-10-20-spore-update.md b/posts/2010-10-20-spore-update.md
index bd363c7..f6c2c63 100644
--- a/posts/2010-10-20-spore-update.md
+++ b/posts/2010-10-20-spore-update.md
@@ -14,54 +14,52 @@ in addition to the already existing implementations:
In this post, I'll try to show some common usages for SPORE, in order to give a better explanation of why I think it's needed.
-Consistency
------------
+## Consistency
-``` example
- require 'Spore'
+``` lua
+require 'Spore'
- local github = Spore.new_from_spec 'github.json'
+local github = Spore.new_from_spec 'github.json'
- github:enable 'Format.JSON'
- github:enable 'Runtime'
+github:enable 'Format.JSON'
+github:enable 'Runtime'
- local r = github:user_information{format = 'json', username = 'schacon'}
+local r = github:user_information{format = 'json', username = 'schacon'}
- print(r.status) --> 200
- print(r.headers['x-runtime']) --> 126ms
- print(r.body.user.name) --> Scott Chacon
+print(r.status) --> 200
+print(r.headers['x-runtime']) --> 126ms
+print(r.body.user.name) --> Scott Chacon
```
``` perl
- use Net::HTTP::Spore;
+use Net::HTTP::Spore;
- my $gh = Net::HTTP::Spore->new_from_spec('github.json');
+my $gh = Net::HTTP::Spore->new_from_spec('github.json');
- $gh->enable('Format::JSON');
- $gh->enable('Runtime');
+$gh->enable('Format::JSON');
+$gh->enable('Runtime');
- my $r= $gh->user_information( format => 'json', username => 'schacon' );
+my $r= $gh->user_information( format => 'json', username => 'schacon' );
- say "HTTP status => ".$r->status; # 200
- say "Runtime => ".$r->header('X-Spore-Runtime'); # 128ms
- say "username => ".$r->body->{user}->{name}; # Scott Chacon
+say "HTTP status => ".$r->status; # 200
+say "Runtime => ".$r->header('X-Spore-Runtime'); # 128ms
+say "username => ".$r->body->{user}->{name}; # Scott Chacon
```
``` ruby
+require 'spore'
- require 'spore'
+gh = Spore.new(File.join(File.dirname(__FILE__), 'github.json'))
- gh = Spore.new(File.join(File.dirname(__FILE__), 'github.json'))
+gh.enable(Spore::Middleware::Runtime) # will add a header with runtime
+gh.enable(Spore::Middleware::Format::JSON) # will deserialize JSON responses
- gh.enable(Spore::Middleware::Runtime) # will add a header with runtime
- gh.enable(Spore::Middleware::Format::JSON) # will deserialize JSON responses
+# API call
+r = gh.user_information( :format => 'json', :username => 'schacon' )
- # API call
- r = gh.user_information( :format => 'json', :username => 'schacon' )
-
- puts "HTTP status => ".r.code # 200
- puts "Runtime => ".r.header('X-Spore-Runtime') # 128ms
- puts "username => ".r.body['user']['name'] # Scott Chacon
+puts "HTTP status => ".r.code # 200
+puts "Runtime => ".r.header('X-Spore-Runtime') # 128ms
+puts "username => ".r.body['user']['name'] # Scott Chacon
```
As you can see in the previous example, I do the same request on the GitHub API: fetch informations from the user "mojombo". In the three languages, the API for the client is the same:
@@ -71,88 +69,84 @@ As you can see in the previous example, I do the same request on the GitHub API:
- you execute your request: the method name is the same, the argument names are the same!
- you manipulate the result the same way
-Easy to switch from a language to another
------------------------------------------
+## Easy to switch from a language to another
You can switch from a language to another without any surprises. If you must provide an API client to a third-party, you don't have to care about what languages they use, you only need to provide a description. Your methods call will be the same between all the languages, so it's easy to switch between languages, without the need to chose an appropriate client for your API (if one exists), to read the documentation (when there is one), and having the client implementation going in your way.
-Better maintanability
----------------------
+## Better maintanability
What annoys me the most when I want to use an API, is that I have to choose between two, three, or more clients that will communicate with this API. I need to read the documentations, the code, and test thoses implementations to decide which one will best fit my needs, and won't go in my way. And what if I need to do caching before the content is deserialized ? And what if the remote API changes it's authentication method (like twitter, from basic auth to OAuth) and the maintainer of the client doesn't update the code ?
With SPORE, you don't have to maintain a client, only a description file. Your API changes, all you have to do is to update your description, and all the clients, using any language, will be able to use your new API, without the need to release a new client specific for this API in javascript, Perl, Ruby, ...
-Easy to use with APIs that are compatible
------------------------------------------
+## Easy to use with APIs that are compatible
If you want to use the Twitter public timeline:
``` perl
- use Net::HTTP::Spore;
+use Net::HTTP::Spore;
- my $client = Net::HTTP::Spore->new_from_spec('twitter.json');
+my $client = Net::HTTP::Spore->new_from_spec('twitter.json');
- $client->enable('Format::JSON');
+$client->enable('Format::JSON');
- my $r = $client->public_timeline( format => 'json' );
+my $r = $client->public_timeline( format => 'json' );
- foreach my $t ( @{ $r->body } ) {
- my $username = Encode::encode_utf8( $t->{user}->{name} );
- my $text = Encode::encode_utf8( $t->{text} );
- say $username . " says " . $text;
- }
+foreach my $t ( @{ $r->body } ) {
+ my $username = Encode::encode_utf8( $t->{user}->{name} );
+ my $text = Encode::encode_utf8( $t->{text} );
+ say $username . " says " . $text;
+}
```
And now on statusnet:
``` perl
- use Net::HTTP::Spore;
+use Net::HTTP::Spore;
- my $client = Net::HTTP::Spore->new_from_spec(
- 'twitter.json',
- base_url => 'http://identi.ca/api'
- );
+my $client = Net::HTTP::Spore->new_from_spec(
+ 'twitter.json',
+ base_url => 'http://identi.ca/api'
+);
- $client->enable('Format::JSON');
+$client->enable('Format::JSON');
- my $r = $client->public_timeline( format => 'json' );
+my $r = $client->public_timeline( format => 'json' );
- foreach my $t ( @{ $r->body } ) {
- my $username = Encode::encode_utf8( $t->{user}->{name} );
- my $text = Encode::encode_utf8( $t->{text} );
- say $username . " says " . $text;
- }
+foreach my $t ( @{ $r->body } ) {
+ my $username = Encode::encode_utf8( $t->{user}->{name} );
+ my $text = Encode::encode_utf8( $t->{text} );
+ say $username . " says " . $text;
+}
```
easy, right ? As both APIs are compatible, the only thing you need to do is change the argument **base\_url** when you create your new client.
-It's easy to write a description
---------------------------------
+## It's easy to write a description
It's really easy to write a description for your API. Let's take a look at the one for github:
-``` example
- {
- "base_url" : "http://github.com/api/v2/",
- "version" : "0.2",
- "methods" : {
- "follow" : {
- "required_params" : [
- "user",
- "format"
- ],
- "path" : "/:format/user/follow/:user",
- "method" : "POST",
- "authentication" : true
- }
- },
- "name" : "GitHub",
- "authority" : "GITHUB:franckcuny",
- "meta" : {
- "documentation" : "http://develop.github.com/"
- }
- }
+``` json
+{
+ "base_url" : "http://github.com/api/v2/",
+ "version" : "0.2",
+ "methods" : {
+ "follow" : {
+ "required_params" : [
+ "user",
+ "format"
+ ],
+ "path" : "/:format/user/follow/:user",
+ "method" : "POST",
+ "authentication" : true
+ }
+ },
+ "name" : "GitHub",
+ "authority" : "GITHUB:franckcuny",
+ "meta" : {
+ "documentation" : "http://develop.github.com/"
+ }
+}
```
The important parts are the basic API description (with a name, a base url for the API) and the list of available methods (here I've only put the 'follow' method).
@@ -161,12 +155,11 @@ More descriptions are available on [GitHub](http://github.com/SPORE/api-descript
We also have [a schema](http://github.com/SPORE/specifications/blob/master/spore_validation.rx) to validate your descriptions.
-Middlewares
------------
+## Middlewares
By default, your SPORE client will only do a request and return a result. But it's easy to alter the default behavior with various middlewares. The most obvious one is the deserialization for a response, like the previous example with github and the middleware Format::JSON.
-### Control your workflow
+## Control your workflow
The use of middlewares allow you to control your workflow as with Plack/Rack/WSGI. You can easily imagine doing this:
@@ -185,32 +178,32 @@ Or to interrogate a site as an API:
- pass the response to a scraper, and put the data in JSON
- return the JSON with scraped data to the client
-### Creating a repository on GitHub
+## Creating a repository on GitHub
In this example, we use a middleware to authenticate on the GitHub API:
``` perl
- use Config::GitLike;
- use Net::HTTP::Spore;
+use Config::GitLike;
+use Net::HTTP::Spore;
- my $c = Config::GitLike::Git->new(); $c->load;
+my $c = Config::GitLike::Git->new(); $c->load;
- my $login = $c->get(key => 'github.user');
- my $token = $c->get(key => 'github.token');
+my $login = $c->get(key => 'github.user');
+my $token = $c->get(key => 'github.token');
- my $github = Net::HTTP::Spore->new_from_spec('github.json');
+my $github = Net::HTTP::Spore->new_from_spec('github.json');
- $github->enable('Format::JSON');
- $github->enable(
- 'Auth::Basic',
- username => $login . '/token',
- password => $token,
- );
+$github->enable('Format::JSON');
+$github->enable(
+ 'Auth::Basic',
+ username => $login . '/token',
+ password => $token,
+);
- my $res = $github->create_repo(
- format => 'json',
- payload => {name => $name, description => $desc}
- );
+my $res = $github->create_repo(
+ format => 'json',
+ payload => {name => $name, description => $desc}
+);
```
The middleware Auth::Basic will add the **authorization** header to the request, using the given tokens.
@@ -220,107 +213,100 @@ The middleware Auth::Basic will add the **authorization** header to the request,
I really like [MooseX::Role::Parameterized](http://search.cpan.org/perldoc?MooseX::Role::Parameterized). This module allows you to build dynamically a Role to apply to your class/object:
``` perl
- package My::App::Role::SPORE;
-
- use MooseX::Role::Parameterized;
- use Net::HTTP::Spore;
-
- parameter name => ( isa => 'Str', required => 1, );
-
- role {
- my $p = shift;
- my $name = $p->name;
-
- has $name => (
- is => 'rw',
- isa => 'Object',
- lazy => 1,
- default => sub {
- my $self = shift;
- my $client_config = $self->context->{spore}->{$name};
- my $client = Net::HTTP::Spore->new_from_spec(
- $client_config->{spec},
- %{ $client_config->{options} },
- );
- foreach my $mw ( @{ $client_config->{middlewares} } ) {
- $client->enable($mw);
- }
- },
- );
- };
-
- 1;
-
- # in your app
-
- package My::App;
-
- use Moose;
-
- with 'My::App::Role::SPORE' => { name => 'couchdb' },
- 'My::App::Role::SPORE' => { name => 'url_solver' };
-
- 1;
+package My::App::Role::SPORE;
+
+use MooseX::Role::Parameterized;
+use Net::HTTP::Spore;
+
+parameter name => ( isa => 'Str', required => 1, );
+
+role {
+ my $p = shift;
+ my $name = $p->name;
+
+ has $name => (
+ is => 'rw',
+ isa => 'Object',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ my $client_config = $self->context->{spore}->{$name};
+ my $client = Net::HTTP::Spore->new_from_spec(
+ $client_config->{spec},
+ %{ $client_config->{options}},
+ );
+ foreach my $mw ( @{ $client_config->{middlewares} } ) {
+ $client->enable($mw);
+ }
+ },
+ );
+};
+1;
+
+# in your app
+package My::App;
+use Moose;
+with 'My::App::Role::SPORE' => { name => 'couchdb' },
+ 'My::App::Role::SPORE' => { name => 'url_solver' };
+1;
```
This Role will add two new attributes to my class: **couchdb** and **url\_solver**, reading from a config file a list of middlewares to apply and the options (like base\_uri).
-Testing my application that uses CouchDB
-----------------------------------------
+## Testing my application that uses CouchDB
This is a common case. In your application you use CouchDB to store some information. When you run the tests for this application, you don't know if there will be a couchdb running on the host, if it will be on the default port, on what database should you do your tests, ...
The Perl implementation of SPORE comes with a Mock middleware:
``` perl
- package myapp;
+package myapp;
- use Moose;
- has couchdb_client => (is => 'rw', isa => 'Object');
+use Moose;
+has couchdb_client => (is => 'rw', isa => 'Object');
- use Test::More;
- use JSON;
+use Test::More;
+use JSON;
- use myapp;
+use myapp;
- use Net::HTTP::Spore;
+use Net::HTTP::Spore;
- my $content = { title => "blog post", website => "http://lumberjaph.net" };
+my $content = { title => "blog post", website => "http://lumberjaph.net" };
- my $mock_server = {
- '/test_database/1234' => sub {
- my $req = shift;
- $req->new_response(
- 200,
- [ 'Content-Type' => 'application/json' ],
- JSON::encode_json($content)
- );
- },
- };
+my $mock_server = {
+ '/test_database/1234' => sub {
+ my $req = shift;
+ $req->new_response(
+ 200,
+ [ 'Content-Type' => 'application/json' ],
+ JSON::encode_json($content)
+ );
+ },
+};
- ok my $client =
- Net::HTTP::Spore->new_from_spec(
- '/home/franck/code/projects/spore/specifications/apps/couchdb.json',
- base_url => 'http://localhost:5984' );
+ok my $client =
+ Net::HTTP::Spore->new_from_spec(
+ '/home/franck/code/projects/spore/specifications/apps/couchdb.json',
+ base_url => 'http://localhost:5984' );
- $client->enable('Format::JSON');
- $client->enable( 'Mock', tests => $mock_server );
+$client->enable('Format::JSON');
+$client->enable( 'Mock', tests => $mock_server );
- my $app = myapp->new(couchdb_client => $client);
+my $app = myapp->new(couchdb_client => $client);
- my $res =
- $app->client->get_document( database => 'test_database', doc_id => '1234' );
- is $res->[0], 200;
- is_deeply $res->[2], $content;
- is $res->header('Content-Type'), 'application/json';
+my $res =
+ $app->client->get_document( database => 'test_database', doc_id => '1234' );
+is $res->[0], 200;
+is_deeply $res->[2], $content;
+is $res->header('Content-Type'), 'application/json';
- done_testing;
+done_testing;
```
The middleware catches the request, checks if it matches something defined by the user and returns a response.
-So ...
-------
+## So ...
I really see SPORE as something Perlish: a glue. The various implementations are a nice addition, and I'm happy to see some suggestions and discussions about the specifications.
diff --git a/posts/2014-02-15-cursive-nrepl-cljs-oh-my.md b/posts/2014-02-15-cursive-nrepl-cljs-oh-my.md
index 996d653..4de858d 100644
--- a/posts/2014-02-15-cursive-nrepl-cljs-oh-my.md
+++ b/posts/2014-02-15-cursive-nrepl-cljs-oh-my.md
@@ -15,15 +15,13 @@ The Intellij IDEA requirement is new. I've never used an IDE before and I've dec
I'm pleased to say that after a few hours I had a completely working environment meeting all my requirements. I'm pretty sure this notes will become outdated quickly, so I'll try update this post as I discover more things.
-The VM
-------
+## The VM
I like Vagrant and there's no reason to not use it for this kind of projects. I've created a [repository](https://github.com/franckcuny/devbox) on GitHub with my setup for that. It's a simple VM with VirtualBox and Vagrant. I'm using Ansible to do the provisioning part.
After cloning the repository, you can run `vagrant up` and a virtual machine, running Ubuntu 13.10, will be started and provisionned (a bunch of tools like tmux are installed, but what really interest us here is the openjdk). Once it's up, a I run `vagrant ssh` then I start a `tmux` session.
-Create a project
-----------------
+## Create a project
I create a project using the [cljs-start](https://github.com/magomimmo/cljs-start) template for leiningen. To be honest, all the hard work is done by this template, I'm just putting the pieces together to have the setup I want build around it. With this template, you get:
@@ -34,8 +32,7 @@ This plugin relies on [Austin](https://github.com/cemerick/austin).
All I needed to do was to run `lein new cljs-start project` (where project is the name of my project).
-The REPL
---------
+## The REPL
Once you've started to work with a REPL it's hard to go back to a language that don't have one. When working with Clojure, you get used very quickly to it, especially since nREPL is so nice, and allows you to work on a remote box.
@@ -47,14 +44,13 @@ The only tricky thing when setting up the VM is to be sure to forward a few port
I run the REPL in headless mode on the VM (in my `tmux` session), from my project's directory:
``` bash
- # specify a port that I will be forwarded from my host to the guest
- AUSTIN_DEFAULT_SERVER_PORT=4343 lein repl :headless :host 0.0.0.0 :port 4242
+# specify a port that I will be forwarded from my host to the guest
+AUSTIN_DEFAULT_SERVER_PORT=4343 lein repl :headless :host 0.0.0.0 :port 4242
```
The `AUSTIN_DEFAULT_SERVER_PORT` variable is the port that will be used by your REPL to talk to the browser. That's why you need to forward this port in Vagrant. The other options (**host** and **port**) are here to tell the repl to listen on all the interfaces (so I can connect from the host) on the given port.
-Editor
-------
+## Editor
> I'm focusing on Intellij IDEA here, but it works the same with Emacs/[CIDER](https://github.com/clojure-emacs/cider).
@@ -62,27 +58,30 @@ To install the Cursive plugin, you need to go to [this page](http://cursivecloju
I can now open a project in Intellij and start coding. I've configured my project to use a remote REPL.
-[/imgs/remote-nrepl.webp](file:///imgs/remote-nrepl.webp) Now I can connect to the remote REPL and do a quick test to see if it works:
+![remote nrepl](../asets/remote-nrepl.webp) Now I can connect to the remote REPL and do a quick test to see if it works:
-[/imgs/test-remote-nrepl.webp](file:///imgs/test-remote-nrepl.webp) Great! It's time to start the web server to serve our static files and see if I can connect the browser-repl to it too. Running the following code in the REPL should do the trick:
+![nrepl](../assets/test-remote-nrepl.webp)
-``` example
- (run) ;; will start a server with jetty on port 3000, that I can reach from port 4000
- (browser-repl) ;; that’s the *really* cool part
+Great! It's time to start the web server to serve our static files and see if I can connect the browser-repl to it too. Running the following code in the REPL should do the trick:
+
+``` clojure
+(run) ;; will start a server with jetty on port 3000, that I can reach from port 4000
+(browser-repl) ;; that’s the *really* cool part
```
If I want to test something, all I have to do is to load the file into the REPL and then call a function. For example:
-``` example
- (.log js/console "Hi from Intellij IDEA!")
+``` clojure
+(.log js/console "Hi from Intellij IDEA!")
```
and see the output in my browser's console!
-[/imgs/nrepl-it-works.webp](file:///imgs/nrepl-it-works.webp) When working on the project, I can run evaluate the file or a form and send it to the browser. Again, this would be the same with Emacs, instead of having CIDER to use a local nREPL session, you'll just connect to a remote one.
+![nplre in browser](../assets/nrepl-it-works.webp)
+
+When working on the project, I can run evaluate the file or a form and send it to the browser. Again, this would be the same with Emacs, instead of having CIDER to use a local nREPL session, you'll just connect to a remote one.
-Conclusion
-----------
+## Conclusion
I realize that it's not the easiest setup. I'm maintaining the build system we have at work for our sites; we use javascript and nodejs, and I'm really upset by the complexity of our process. If I had to put with all of that to build a site I would be pretty mad. Still, I think this setup can be simplified a lot. But using a VM also makes it easier to give a working environment to a new developer, and it's easy to throw it away, after all, I'm using it mostly to run the REPL and to have it working in an environment similar to what it would be in production.