2016/07/07

Cloudant Analizers

The other day I rewrote CloudanDB (Which is in Japanese 日本語) and realized that some things were not working because of the analizer I was using. So this post is about them.

What are Cloudant Analizers?

From the official documentation:
Analyzers are settings which define how to recognize terms within text. This can be helpful if you need to index multiple languages.
I decided to try some of the analyzers and put my results here. Test text has two completely different languages (English and Japanese) and some other symbols which I think it is enough to find out how different analysers behave.

How to test Analizers

The _search_analyze API is for this purpose
FILE=analyze_classic.json
echo '{"analyzer": "classic","text": "Evening of the seventh"}' > $FILE

# Use _search_analyze
CLOUDANT='https://<username>:<password>@<username>.cloudant.com'
curl -X POST ${CLOUDANT}/_search_analyze  -H 'Content-Type: application/json' --data @${FILE}
{"tokens":["evening","seventh"]}

Experiment

Today is a Japanese festival Tanabata so I will use a piece of this romantic song I like very much Suspicious Mind - Elvis Presley and a part of its japanese translation I found here
We're caught in a trap\n
I can't walk out\n
Because I love you too much baby♬.\n
Sent by nacho@email.com at 2016-07-07 18:00:29 +0900 ★\n
はまった罠から\n
出られないんだ\n
ほんとに君に首ったけなんだ♬.\n
2016年7月7日18時0分29秒にnacho@email.jpより ★
I added new lines \n, ♬★ marks, an email and a timestamp to make it more interesting ;-]

Results

AnalyzerResult tokensLength
classic["we're", "caught", "trap", "i", "can't", "walk", "out", "because", "i", "love", "you", "too", "much", "baby", "sent", "nacho@email.com", "2016-07-07", "18", "00", "29", "0900", "は", "ま", "っ", "た", "罠", "か", "ら", "出", "ら", "れ", "な", "い", "ん", "だ", "ほ", "ん", "と", "に", "君", "に", "首", "っ", "た", "け", "な", "ん", "だ", "2016", "年", "7", "月", "7", "日", "18", "時", "0", "分", "29", "秒", "に", "nacho@email.jp", "よ", "り"]64
email["we're", "caught", "trap", "i", "can't", "walk", "out", "because", "i", "love", "you", "too", "much", "baby", "sent", "nacho@email.com", "2016", "07", "07", "18", "00", "29", "0900", "は", "ま", "っ", "た", "罠", "か", "ら", "出", "ら", "れ", "な", "い", "ん", "だ", "ほ", "ん", "と", "に", "君", "に", "首", "っ", "た", "け", "な", "ん", "だ", "2016", "年", "7", "月", "7", "日", "18", "時", "0", "分", "29", "秒", "に", "nacho@email.jp", "よ", "り"]66
keyword["We're caught in a trap\nI can't walk out\nBecause I love you too much baby♬.\nSent by nacho@email.com at 2016-07-07 18:00:29 +0900 ★\nはまった罠から\n出られないんだ\nほんとに君に首ったけなんだ♬.\n2016年7月7日18時0分29秒にnacho@email.jpより ★"]1
simple["we", "re", "caught", "in", "a", "trap", "i", "can", "t", "walk", "out", "because", "i", "love", "you", "too", "much", "baby", "sent", "by", "nacho", "email", "com", "at", "はまった罠から", "出られないんだ", "ほんとに君に首ったけなんだ", "年", "月", "日", "時", "分", "秒にnacho", "email", "jpより"]35
standard["we're", "caught", "trap", "i", "can't", "walk", "out", "because", "i", "love", "you", "too", "much", "baby", "sent", "nacho", "email.com", "2016", "07", "07", "18", "00", "29", "0900", "は", "ま", "っ", "た", "罠", "か", "ら", "出", "ら", "れ", "な", "い", "ん", "だ", "ほ", "ん", "と", "に", "君", "に", "首", "っ", "た", "け", "な", "ん", "だ", "2016", "年", "7", "月", "7", "日", "18", "時", "0", "分", "29", "秒", "に", "nacho", "email.jp", "よ", "り"]68
whitespace["We're", "caught", "in", "a", "trap", "I", "can't", "walk", "out", "Because", "I", "love", "you", "too", "much", "baby♬.", "Sent", "by", "nacho@email.com", "at", "2016-07-07", "18:00:29", "+0900", "★", "はまった罠から", "出られないんだ", "ほんとに君に首ったけなんだ♬.", "2016年7月7日18時0分29秒にnacho@email.jpより", "★"]29
english["we'r", "caught", "trap", "i", "can't", "walk", "out", "becaus", "i", "love", "you", "too", "much", "babi", "sent", "nacho", "email.com", "2016", "07", "07", "18", "00", "29", "0900", "は", "ま", "っ", "た", "罠", "か", "ら", "出", "ら", "れ", "な", "い", "ん", "だ", "ほ", "ん", "と", "に", "君", "に", "首", "っ", "た", "け", "な", "ん", "だ", "2016", "年", "7", "月", "7", "日", "18", "時", "0", "分", "29", "秒", "に", "nacho", "email.jp", "よ", "り"]68
spanish["we'r", "caught", "in", "trap", "i", "can't", "walk", "out", "becaus", "i", "love", "you", "too", "much", "baby", "sent", "by", "nach", "email.com", "at", "2016", "07", "07", "18", "00", "29", "0900", "は", "ま", "っ", "た", "罠", "か", "ら", "出", "ら", "れ", "な", "い", "ん", "だ", "ほ", "ん", "と", "に", "君", "に", "首", "っ", "た", "け", "な", "ん", "だ", "2016", "年", "7", "月", "7", "日", "18", "時", "0", "分", "29", "秒", "に", "nach", "email.jp", "よ", "り"]71
japanese["we", "re", "caught", "in", "a", "trap", "i", "can", "t", "walk", "out", "because", "i", "love", "you", "too", "much", "baby", "sent", "by", "nacho", "email", "com", "at", "2016", "07", "07", "18", "00", "29", "0900", "はまる", "罠", "出る", "ほんとに", "君", "首ったけ", "2", "0", "1", "6", "年", "7月", "7", "日", "1", "8", "時", "0", "分", "2", "9", "秒", "nacho", "email", "jp"]56
cjk["we're", "caught", "trap", "i", "can't", "walk", "out", "because", "i", "love", "you", "too", "much", "baby", "sent", "nacho", "email.com", "2016", "07", "07", "18", "00", "29", "0900", "はま", "まっ", "った", "た罠", "罠か", "から", "出ら", "られ", "れな", "ない", "いん", "んだ", "ほん", "んと", "とに", "に君", "君に", "に首", "首っ", "った", "たけ", "けな", "なん", "んだ", "2016", "年", "7", "月", "7", "日", "18", "時", "0", "分", "29", "秒に", "nacho", "email.jp", "より"]63
arabic["we're", "caught", "in", "a", "trap", "i", "can't", "walk", "out", "because", "i", "love", "you", "too", "much", "baby", "sent", "by", "nacho", "email.com", "at", "2016", "07", "07", "18", "00", "29", "0900", "は", "ま", "っ", "た", "罠", "か", "ら", "出", "ら", "れ", "な", "い", "ん", "だ", "ほ", "ん", "と", "に", "君", "に", "首", "っ", "た", "け", "な", "ん", "だ", "2016", "年", "7", "月", "7", "日", "18", "時", "0", "分", "29", "秒", "に", "nacho", "email.jp", "よ", "り"]72

Commentaries

Some interesting things to note:
  • The only notorious difference between standard and classic is the treat of "2016-07-07" and emails. standard got the emails wrong.
  • email looks like standard but with the emails right
  • whitespace looks like a good option when text has symbols (ie.: ♬, ★). There are still there as tokens!
  • keyword just one token. I think it would be useful for exact match searches
  • None of the analyzers, not even keyword preserved the new line character \n.
  • Several occurrences of the same string are valid. (ie.: "i" and "★")
  • I find funny "we're" became "we'r" when english was used. Also "baby" became "babi".
  • Japanese words were tokenized as characters in english. Makes really hard (if possible) to do a useful search using Japanese
  • I am depressed that spanish didn't get my name "nacho" right. For some reason it became "nach". I guess it is trying to get the root of it, since there is also nacha, nachito, nachita, nachos, nachas, etc.
  • cjk (chinese/japanese/korean) complete broke the japanese words. I know it is hard to parse Japanese but cjk is not helping at all here.
  • I have no idea why I tried arabic I literally have 0 knowledge of the language to comment something about it.


More on "nacho"
curl -X POST ${CLOUDANT}/_search_analyze  -H 'Content-Type: application/json' -d '{"analyzer":"spanish", "text":"nacho"}'
{"tokens":["nach"]}
curl -X POST ${CLOUDANT}/_search_analyze  -H 'Content-Type: application/json' -d '{"analyzer":"spanish", "text":"nachos"}'
{"tokens":["nach"]}
curl -X POST ${CLOUDANT}/_search_analyze  -H 'Content-Type: application/json' -d '{"analyzer":"spanish", "text":"Nachos"}'
{"tokens":["nach"]}
🇵🇪 Creo que voy a reclamar a los de Cloudant o Apache para que mi nombre salga bien! Ahorita mismo!!🇵🇪 (O sera que esta sacando la raiz de la palabra?)

2016/07/04

Cordova application skeleton

Create application

Create it using the cordova command. We can get more help with: cordova create -help.

# cordova create <PATH> [ID [NAME [CONFIG]]] [options]
cordova create hello com.example.hello HelloWorld
cd hello

Next, we add platforms. Here I only add iOS and Android but there are a lot more.

cordova platform add ios --save
cordova platform add android --save

Working with git

I am not really into Cordova applications but as any other platforms I tend to have my repositories the smallest possible. There should be a command that downloads all dependencies so all users are able to have the same environment in the end. For such reason I ignore everything in platforms/ and plugins/ directories

My .gitignore:

platforms/
plugins/

The command that will make my environment ready will be:

cordova prepare

It copies files for specified platforms, or all platforms, so that the project is ready to build with each SDK.

So far our file structure will look like this:

.
├── config.xml
├── hooks/
│   └── README.md
├── platforms/
│   ├── android/
│   ├── ios/
│   └── platforms.json
├── plugins/
│   ├── android.json
│   ├── cordova-plugin-whitelist/
│   ├── fetch.json
│   └── ios.json
└── www/
    ├── css/
    ├── img/
    ├── index.html
    └── js/

Running the app

On iOS

Install ios-sim to be able to lunch the simulator. This is a one time task.

npm install -g ios-sim

Build and run

cordova build ios # build Xcode project
cordova emulate ios # launch simulator

Using Xcode

Command cordova build ios creates an Xcode project. This is quite useful specially for debbuging plugins.

 open -a Xcode platforms/ios/HelloWorld.xcodeproj/

On Android

Build and run

cordova build android
cordova run android # Run on connected device
cordova emulate android # Run on simulator

Using Android studio

Command cordova build android, creates an Android Studio project. Again, this is quite useful for debbuging plugins.

open -a "Android Studio" platforms/android/

2016/06/28

Table of contents generator in javascript

There are a lot of approaches arround. Lets add one more to the pile:

Javascript

No need of jquery or other stuff. Just plain and simple vanilla js ;]

function TableOfContents(tocId, containerId) {
    var toc = document.getElementById(tocId);
    toc.innerHTML = "";
    toc.className += " toc";
    var container = containerId ? document.getElementById(containerId) : toc.parentElement;
    var headings = [].slice.call(container.querySelectorAll('h1, h2, h3, h4, h5, h6'));
    var closeLevel = function(e, levels) {
        for (var i = 0; i < levels && e.parentElement && e.parentElement.parentElement; i++) {
            e = e.parentElement.parentElement;
        }
        return e;
    };
    var createLiWithAnchor = function(anchor, heading) {
        var li = document.createElement("li");
        var a = document.createElement("a");
        a.href = "#" + anchor;
        a.textContent = heading.textContent;
        li.appendChild(a);
        heading.innerHTML = "<a name='" + anchor + "'>" + heading.textContent + "</a>";
        heading.className += " no-decoration";
        return li;
    };
    var prevLevel = 0;
    var root, curr;
    headings.forEach(function (heading, index) {
        var tag = heading.tagName.toLowerCase();
        var curLevel = parseInt(tag.replace(/[^\d]/i, ""));// get number from h1, h2, h3,... tags
        var anchor = heading.textContent.replace(/\r?\n|\r/g, "").replace(/\s/, "_"); // remove all new lines and replace spaces with underscore
        var li = createLiWithAnchor(anchor, heading);
        if (curLevel > prevLevel) {
            // open 1 ul and add 1 li
            if (!curr) {
                root = document.createElement("ol");
                root.appendChild(li);
            } else {
                var ul = document.createElement("ul");
                ul.appendChild(li);
                curr.appendChild(ul);
            }
        } else if (curLevel === prevLevel) {
            // add 1 new li next to current li
            curr.parentElement.appendChild(li);
        } else if (curLevel < prevLevel) {
            // close n ul, add one 1 li as a sibling of the ancestor
            var ancestor = closeLevel(curr, prevLevel - curLevel);
            ancestor.parentElement.appendChild(li);
        }
        curr = li;
        prevLevel = curLevel;
    });
    toc.appendChild(root);
}
If you are pasting this into blogger, the function must be XML escaped. This site helped a lot

Css

So it looks a bit better.
/* ----- TOC ----- */
.toc {
background-color: #dadada;
border: gray solid 1px;
margin-bottom: 1em;
}
.toc .toc-title {
margin-left: 1em;
}
.post-body .no-decoration,
.post-body .no-decoration a {
text-decoration: none;
color: black;
}

Usage

Prepare a container

<div id="mytoc"></div>

Call the script

<script type="text/javascript">
TableOfContents("mytoc", "articleId");
</script>
If you using blogger just put the container div at the top of your post and omit the last parameter, the function will assume the article is the parent of the TOC container.

Result

2016/05/17

Gitlab merge request from terminal

This is not a fully automated command for making merge request, instead it is tiny helper command that will take you to the right place in the browser to complete your merge request with one click.


BTW, Why not to do everything with Gitlab API?
It is fully possible. Actually I was trying to do so but in the way I realized that is not what I need because in a merge request you want to set a description (writing a description in the terminal is not better than doing it in the browser), you also will want to assign somebody to review your code (searching for other gitlab users in the terminal is not easier than the browser pulldown menu), you probably want to have a milestone, and maybe other details...

If you still want to do it fully programatically I have some hints at the end of this post :p

Script

#!/usr/bin/env bash -e

# Get host, group and repository names
REMOTE_LINE=`git remote get-url --push origin`
if [[ $REMOTE_LINE == "git@"* ]]; then
    # git: git@my.gitlab.host.com:my-group/project.git
    HOST=$(expr "$REMOTE_LINE" : 'git@\(.*\):.*')
    GROUP=$(expr "$REMOTE_LINE" : 'git@.*:\(.*\)\/.*')
    PROJECT_NAME=$(expr "$REMOTE_LINE" : 'git@.*:.*\/\(.*\)\.git')
elif [[ $REMOTE_LINE == "https://"* ]]; then
    # https: https://my.gitlab.host.com/my-group/project.git
    HOST=$(expr "$REMOTE_LINE" : 'https:\/\/\([^\/]*\)\/.*')
    GROUP=$(expr "$REMOTE_LINE" : 'https:\/\/[^\/]*\/\(.*\)\/.*')
    PROJECT_NAME=$(expr "$REMOTE_LINE" : 'https:\/\/.*\/\(.*\)\.git')
else
    echo Unknown protocol from $REMOTE_LINE
    exit 1
fi

# Get current branch name
BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Create new merge request from current branch to default branch
# (Add `-a /Applications/Firefox.app` to open it with a specific browser)
open https://${HOST}/${GROUP}/${PROJECT_NAME}/merge_requests/new?merge_request[source_branch]=${BRANCH}

Setup

Copy above script as git-mr and place it somewhere in the $PATH and make sure it has executable permissions using chmod
chmod +x ~/.bin/git-mr

Usage

git push origin -u feature/myfeature
git mr
Voilà!
(browsers opens and you are just one click away from completing your merge request)

If you still want to do it programatically with Gitlab API, we need a project id an authentication token to be able to call create merge request API.
POST /projects/:id/merge_requests

Get Authentication Token

We can request the user to enter his/her login and password once and store that in the keychain. Below code is how to retrieve a previously stored password from the keychain
# Get credentials
SECOUT=`security 2>&1 find-internet-password -gs ${HOST}`
LOGIN=`expr "$SECOUT" : '.*"acct"="\([^"]*\)"'`
PASSWORD=`expr "$SECOUT" : '.*password: "\([^"]*\)"'`
echo $LOGIN $PASSWORD
With such information we can call session API and get the token for the upcomming requests
#Initialize a session
SESSIONOUT=`curl -X POST "https://${HOST}/api/v3/session?login=${LOGIN}&password=${PASSWORD}"`
TOKEN=`expr "$SESSIONOUT" : '.*"private_token":"\([^"]*\)"'`
echo $TOKEN

Get Project ID

I have tried doing ${GROUP}/${PROJECT_NAME} and ${GROUP}%2F${PROJECT_NAME} without luck so I got the project by using project search API. Unfortunately gitlab search API is not very powerful yet so we need to search results and get the id of the project that exactly matches our project. Here I am using jq
PROJECT_ID=$(curl --header "PRIVATE-TOKEN: ${TOKEN}" 'https://${HOST}/api/v3/projects/?search=${PROJECT_NAME}' | jq '.[] | select( .path_with_namespace == '\"${GROUP}/${PROJECT_NAME}\"' ) | .id')
Now we are almost ready!

Call merge_request API

# Disabled code: Get tracking branch name
#BRANCH=$(git rev-parse --abbrev-ref --symbolic-full-name @{u})
#BRANCH=${TARGET_BRANCH##origin/}

# Get remote target branch
TARGET_BRANCH='develop'

curl --header "PRIVATE-TOKEN: ${TOKEN}" -X POST "https://${HOST}/projects/${PROJECT_ID}/merge_request" --data "source_branch=${BRANCH}" --data "target_branch=${TARGET_BRANCH}" --data "title=Implementation of my feature"
phew...

2016/05/12

Dictionary to JSON string and JSON string to dictionary

Because I always end up Googling NSJSONSerialization and its options...

Dictionary to JSON string

The dictionary is converted to NSData which contains an UTF8 encoded string inside.
let dictionary: [String: AnyObject] = ["nacho": ["1","2","3"] ]
let jsonData = try? NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions())
let jsonString = NSString(data: jsonData!, encoding: NSUTF8StringEncoding)
print(jsonString)
{"nacho":["1","2","3"]}
Also it is possible to use .PrettyPrinted

JSON String to Dictionary

The JSON string should be converted to NSData (using UTF8 encoding), then we can create a dictionary from such data.
let jsonString = "{\"nacho\":[\"1\",\"2\",\"3\"]}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
let dictionary = try? NSJSONSerialization.JSONObjectWithData(jsonData!, options: .MutableLeaves)
print(dictionary!)
{\n    nacho =     (\n        1,\n        2,\n        3\n    );\n}
In NSJSONReadingOptions there is also .MutableLeaves and .AllowFragments but I barely use them

2016/04/25

Registering client filters for JAX-RS ClientBuilder

I write this here so I don't forget some of the goodies in below packages :)
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.Invocation.*;
Helpful Links

ClientRequestFilter

public class RequestFilter implements ClientRequestFilter {

    private static final Logger LOG = Logger.getLogger(RequestFilter.class.getName());

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        LOG.log(Level.INFO, ">> " + requestContext.getMethod() + " " + requestContext.getUri());
        LOG.log(Level.INFO, ">> (body) " + requestContext.getEntity().toString());
        LOG.log(Level.INFO, ">> (headers) " + requestContext.getHeaders().toString());
    }
}

ClientResponseFilter

public class ResponseFilter implements ClientResponseFilter {

    private static final Logger LOG = Logger.getLogger(ResponseFilter.class.getName());

    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
        LOG.log(Level.INFO, "<< " + requestContext.getMethod() + " " + requestContext.getUri());
        LOG.log(Level.INFO, "<< (status) " + responseContext.getStatus());
        LOG.log(Level.INFO, "<< (headers) " + responseContext.getHeaders().toString());

        InputStream entityStream = responseContext.getEntityStream();
        if (entityStream != null) {
            LOG.log(Level.FINER, "<< (body)" + entityStream.toString());
        }
    }
}

Usage

JSONObject body = new JSONObject();
body.put("key", "value");

Client client = ClientBuilder.newClient();
client.register(ResponseFilter.class); // Response logging
client.register(new RequestFilter()); // Request logging

Response response = client.target("https://api.example.com")
    .path("clients")
    .request()
    .buildPost(Entity.json(body))
    .invoke();

if (response.getStatus() == 200) { ... } else { ... }

2016/04/23

Setup Liberty Server on Eclipse

Instructions for setting up a Liberty Server in Eclipse Mars 4.5.2 (Eclipse IDE for Java EE Developers). The following instructions assume IBM WebSphere Application Server Liberty Developer Tools Beta has been installed from Eclipse Market Place.

Creating a new Liberty Server

  1. Create a server using the Wizard.
    Click File > New > Server
    Click Next
  2. Select WebSphere Application Server Liberty
    Click Next
  3. Select Install from an archive or repository
    Click Next
  4. Enter the Destination path (This will be the place where the server will live)
    Select Download and install a new runtime environment from ibm.com
    Select WAS Liberty V8.5.5.9 with Java EE 7 Full Support
    Click Next
  5. Click Install for the following bundles:
    • Base Bundle
    • Liberty Core Bundle
    • V9 Bundle for Java EE 7 Full Support
    • V9 Bundle for Java EE 7 Web Profile
    Click Next
  6. Accept the terms
    Click Next
  7. Select Stand-alone server
    Click Next
  8. Enter a name for your server. I usually leave it as defaultServer
    Click Finish
  9. Finally a confirmation alert will show. Now we have our Liberty server installed in Eclipse. Almost ready :)

Adding a simple keyStore

Since we have javaee-7.0 feature enabled we need to add a keystore, otherwise we will not get rid of the following error:

The enabled features require that a keyStore element and a user registry are defined in the server configuration. Use the server configuration editor to add these items.
We can do as the comments and create a properly encoded password but for development we can do just:
<keyStore id="defaultKeyStore" password="keyStorePwd"/>

2016/04/02

JAX-RS Maven project for WAS Liberty

So the other day I wrote this, apparently not a good approach these days. This is probably the official (current) way of creating a web app project. The following instructions are specific for WebSphere Application Server Liberty Profile. Much of these information is based on the following sites/posts/answer:

Create a Maven project

This could be created manually, but Eclipse is good at this :)
  1. FileNewMaven Project
  2. Check Use default Workspace location
    Click next
  3. Select the http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/wasdev/maven/repository Catalog
    Select the com.ibm.tools.archetype webapp-jee7-liberty
    Click next
  4. Enter a Group Id: com.ibm.jp.myproject
    Enter a Artifact Id: myproject
    Enter Package: com.ibm.jp.myproject
    Click Finish
At this point we have created the project but it seems it is a bit broken. At written here we can get rid of the following error by modifying the pom.xml
ArtifactDescriptorException: Failed to read artifact descriptor for com.ibm.tools.target:was-liberty:pom:LATEST: VersionResolutionException: Failed to resolve version for com.ibm.tools.target:was-liberty:pom:LATEST: Could not find metadata com.ibm.tools.target:was-liberty/maven-metadata.xml in local (/Users/paulbastide/.m2/repository)

Modify the pom.xml

  1. To get rid of above error add the following to pom.xml
    <repositories>
        <repository>
            <id>maven online</id>
            <name>maven repository</name>
            <url>https://repo1.maven.org/maven2/</url>
            </repository>
            <repository>
            <id>liberty maven online</id>
            <name>liberty maven repository</name>
            <url>http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/wasdev/maven/repository/</url>
        </repository>
    </repositories>
    
  2. Setup to use Dynamic Web Module 3.1 Add the following to pom.xml
    <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.6</version>
        <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
    </plugin>
    
    also
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    
    Also we need to make sure 3.1 is setup properly in web.xml (For other versions check Katsumi Kokuzawa's Blog)
    <!DOCTYPE xml>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                                 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
        <display-name>Servlet 3.1 Web Application</display-name>
    </web-app>
    
  3. Setup to use Java 7 or 8 (needed for Dynamic Web Module 3.1)
    For Java 7
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.3</version>
        <configuration>
            <source>1.7</source>
            <target>1.7</target>
        </configuration>
    </plugin>
    
    For Java 8 we need to use the 3.5.1 version of the plugin
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
    

Update the project

After all the changes we just do right click in the project MavenUpdate Project ...
If we check the Project Facets we should have
  • Dynamic Web Module 3.1
  • Java 1.8
  • JAX-RS(Rest Web Services) 2.0

Here is a copy of the full pom.xml and web.xml files :)

2016/03/21

Create a JAX-RS maven project and import it into Eclipse

See updated instructions here in this other post

The following instructions work but are Not Recommended since modifying the pom.xml would be useless since those modifications will not be reflected in Eclipse project :(
  1. Create the skeleton
    Instructions taken from Stackoverflow - Maven 3 failed to execute goal apache ...
    mvn archetype:generate \
        -DgroupId={project-packaging} \
        -DartifactId={project-name} \
        -DarchetypeArtifactId=maven-archetype-webapp \
        -DinteractiveMode=false
    
    That will create a {project-name} directory with the contents:
    .
    ├── pom.xml
    └── src
        └── main
            ├── java (This directory will contain all the source code, must be created manually)
            ├── resources
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
  2. Import it into eclipse
    FileImportExisting Projects into Workspace and then select {project-name} directory. Once imported, go to project properties and do the following settings:
    1. Setup the facets
      Instructions taken from this article from OPENTONE and adapted for my needs
      • Java 1.8
      • Dynamic Web Project 3.1
      • JAX-RS 2.0
      • Javascript will be selected automatically later. It is not necessary to explicitly select it here
    2. Setup the target runtime
      Select "WebSphere Liberty Profile"

2016/03/20

Convert flac to aac with ffmpeg

I write this so I don't forget (Original Superuser thread)
  • Install ffmpeg with libfaac support
    brew reinstall ffmpeg --with-faac;
    
  • Convert them
    cd flac_directory
    for f in *.flac; do ffmpeg -i "$f" -acodec libfaac -aq 400 "${f%flac}m4a"; done
    
Certain files will throw an error: [libx264 @ 0x2f08120] height not divisible by 2 (500x489) In such case adding the following option did the trick for me:
-vf "scale=640:-2"
Taken from Stackoverflow - Maintaining ffmpeg aspect ratio

2016/02/28

Swift en el servidor con Kitura

Este post es 90% una traducción de esta pagina en Japonés que lo explica muy bien.

Recientemente IBM esta llevando Swift al servidor y para esto ha publicado varias herramientas. Kitura es uno de estas, es un framework para aplicaciones, es open-source y esta basado en Express.js, el popular framework de nodejs.


Prerequisitos

  1. Instalamos unas dependencias usando homebrew
    brew install http-parser pcre2 curl hiredis
  2. Instalamos el último compilador swift de la página official de Swift.org: Swift - Latest Development Snapshots. No se preocupen, ya esta compilado y viene en un archivo listo para instalar .pkg.
    Luego de la instalación agregamos la siguiente locación al PATH modificando el archivo ~/.bash_profile
    export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:$PATH
    

Nuestro projecto

  1. Creamos un directorio para nuestro proyecto e iniciamos este con el comando swift build
    mkdir swift-sample && cd swift-sample
    swift build --init
    
  2. Modificamos el archivo Package.swift:
    import PackageDescription
     
    let package = Package(
        name: "myFirstProject",
        dependencies: [
            .Package(url: "https://github.com/IBM-Swift/Kitura-router.git", majorVersion: 0),
        ]
    )
    
    Y con estos cambios instalamos de nuevo nuestras dependencias:
    swift build
    
    Algunos se habrán dado cuento que swift build es como un equivalente al npm install en nodejs or bundle install en ruby. Esta vez saldrán mas mensajes y al final terminará en error. Pero no preocuparse, es por que Kitura router depende un una librería en C y el package manager no soporta el linkage C/C++ todavia. Este error tambien esta descrito en el README de Kitura

    ld: library not found for -lcurlHelpers for architecture x86_64
    :0: error: link command failed with exit code 1 (use -v to see invocation)
    :0: error: build had 1 command failures
    error: exit(1): ...
    
  3. Copiamos el Makefile de Kitura-net. En mi caso es la version 0.3.2, usar la version que cada uno tenga :]
    cp Packages/Kitura-net-0.3.2/Makefile-client Makefile
    
  4. Ahora ya estamos listos para escribir nuestro codigo!. Modificamos el archivo Sources/main.swift
    import KituraRouter
    import KituraNet
    import KituraSys
     
    let router = Router()
    
    router.get("/") { request, response, next in
        response.status(HttpStatusCode.OK).send("Hello, World!")
        next()
    }
     
    let server = HttpServer.listen(8090, delegate: router)
    Server.run()
    
  5. Compilamos nuestro pequeño proyecto
    make
    
  6. Ejecutamos el archivo ejecutable que acabamos de crear
    .build/debug/myFirstProject
    
  7. Ahora vayamos a nuestro explorador o con curl
    curl http://localhost:8090/
    
    Hello, World!
    
Por ahora son varios pasos, esperemos con el paso del tiempo todo es sea mas fácil. Yo ya estoy contento con esto!

2016/01/18

CSSでSVGを表示する方法の3つ

このようなsvgを表示したい とすると 幾つかのアプローチが考えられる。

直接CSSに入れ込む

一番楽チンで IEでサポートされず無視されるからプロトタイプの時に便利。バックスラッシュで改行可能

CSS:

.move {
    background-image: url('data:image/svg+xml;utf8,\
    <svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">\
    <g transform="translate(7, 7) rotate(135)">\
    <line x1="-0.5" y1="0" x2="0.5" y2="0" stroke="rgba(255,0,0,0.5)" stroke-width="1"/>\
    <line x1="-2.5" y1="2" x2="2.5" y2="2" stroke="rgba(255,0,0,0.5)" stroke-width="1"/>\
    <line x1="-4.5" y1="4" x2="4.5" y2="4" stroke="rgba(255,0,0,0.5)" stroke-width="1"/>\
    </g></svg>');
}

ファイルを用意してurlを指定する

おそらく一番一般的でしょう。クローズブラウザー。描画の祭にリクエストが発生するが(他のリソースと同様に)ブラウザーのキャッシュが効き複数回描画が必要になったとしてもリクエストは一個のみ。

SVGファイル(例えばmyfile.svg)

<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
    <g transform="translate(7, 7) rotate(135)">
        <line x1="-0.5" y1="0" x2="0.5" y2="0" stroke="rgba(255,0,0,0.5)" stroke-width="1"/>
        <line x1="-2.5" y1="2" x2="2.5" y2="2" stroke="rgba(255,0,0,0.5)" stroke-width="1"/>
        <line x1="-4.5" y1="4" x2="4.5" y2="4" stroke="rgba(255,0,0,0.5)" stroke-width="1"/>
    </g>
</svg>

CSS:

.move {
    background-image: url("myfile.svg")
}

Base64でエンコードしてCSSに入れ込む

クローズブラウザー。リクエストは発生しない。普通の人間は多分読めないし、エンコードして短くなったわけではありません。 おそらくプログラムでコンテンツを作成してbtoaでバイナリに変換してbackground-imageとして表示したい時などに有効な方法でしょうかね

(最初間違って;base64,を書いていなかったから 何も表示されなかったですね。@rikuoさんご教授をありがとうございます!)

CSS:

.move {
    background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4IiBoZWlnaHQ9IjgiIHZpZXdCb3g9IjAgMCA4IDgiPg0KICAgIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDcsIDcpIHJvdGF0ZSgxMzUpIj4NCiAgICAgICAgPGxpbmUgeDE9Ii0wLjUiIHkxPSIwIiB4Mj0iMC41IiB5Mj0iMCIgc3Ryb2tlPSJyZ2JhKDI1NSwwLDAsMC41KSIgc3Ryb2tlLXdpZHRoPSIxIi8+DQogICAgICAgIDxsaW5lIHgxPSItMi41IiB5MT0iMiIgeDI9IjIuNSIgeTI9IjIiIHN0cm9rZT0icmdiYSgyNTUsMCwwLDAuNSkiIHN0cm9rZS13aWR0aD0iMSIvPg0KICAgICAgICA8bGluZSB4MT0iLTQuNSIgeTE9IjQiIHgyPSI0LjUiIHkyPSI0IiBzdHJva2U9InJnYmEoMjU1LDAsMCwwLjUpIiBzdHJva2Utd2lkdGg9IjEiLz4NCiAgICA8L2c+DQo8L3N2Zz4=")
}

2015/10/10

Setup Komodo with Go

Komodo IDE looks great but I needed to explicitly setup some stuff to make if work with Go. I believe these settings are necessary because Komodo cannot read my GOPATH definition which was done in ~/.bash_profile. Also because I didn't define GOBIN as it is not mandatory. Hence various tools are not found :(
  • Add GOPATH environment variable

    echo $GOPATH
    
    /Users/ignacio/Documents/goworkspace
    
  • Set godef and gocode

    # Install godef and gocode if needed
    go get -v github.com/rogpeppe/godef
    go get -u github.com/nsf/gocode
    
    • I realised that it has been a couple of months I didn't update anything. brew update; brew upgrade gave me go 1.5.1 so I had to re-compile godef again:
      cd $GOPATH/src/github.com/rogpeppe/godef
      go clean -r -i
      go install -v
      
    Now we can add their paths to Komodo. If go binary is not in the $PATH you can set up manually too but it should be. Their location are ${GOPATH}/bin/godef and ${GOPATH}/bin/gocode
Now restart Komodo :)

2015/08/29

grc

Install

general coloriser can be installed via homebrew:
brew install grc

Configuration

We need a general configuration file ~/.grc/grc.conf where we write a regex and a filename per kind of command we call with grc. For example to colorise:
tail -f messages.log
We will call
grc tail -f messages.log
grc will look for entries in ~/.grc/grc.conf that match tail -f messages.log and use the file of the same entry to color the output

Content of ~/.grc/grc.conf

# Colorise messages.log
\b\w+\b.*messages\.log\b
messages.log.conf

Content of ~/.grc/messages.log.conf

# Informative logs
regexp=^\[.*\]\s[\dA-z]+\s(.*)\s+[A]\s(.*)$
colours=cyan
count=line
=====
# More informative logs
regexp=^\[.*\]\s[\dA-z]+\s(.*)\s+[I]\s(.*)$
colours=unchanged
count=line
=====
# Error
regexp=^\[.*\]\s[\dA-z]+\s(.*)\s+[ER]\s(.*)$
colours=red
count=line
=====
# Stacktraces (start with tab)
regexp=^\[.*\]\s[\dA-z]+\s(.*)\s+[R]\s(\t.*)$
colours=none, none, red
count=line

Result

2015/08/24

TernJS

Ternjs is awesome tool for working with javascript. It provides autocompletion not only for current file but also for the entire project! Ternjs is made of a server (a daemon) that will read javascript files and will return completions/candidates upon client requests. Clients are usually plugins for editors like emacs, sublime text, etc. Here I show the emacs settings

Install server

Via npm is the easiest way:
npm install -g tern

Install the client

  • Install needed packages in emacs
    Install tern-mode and tern-auto-complete via package-list-packages
  • Test server communication
    This is optional. It is to check server and client can establish communication.
    Open a js file on emacs and activate tern mode M-x tern-mode. It will start the server automcatically and a message like the following will appear:
    Making url-show-status local to `http 127.0.0.1:58767` while let-bound!
  • Make a .tern-project file
    This is optional but recommended. Ternjs uses a .tern-project file to understand the project. Ternjs can be used to do nodejs or the usual client web development. If no .tern-project is found then a fallback will be loaded. You can find more in the manual. This is mine:
    {
        "libs": [
            "browser",
            "jquery"
        ],
        "loadEagerly": [
            "js/api.js",
            "js/array-helper.js",
            "js/data-provider.js",
            "js/view-stack.js"
        ]
    }
    
  • Tell emacs to use tern always This is also optional. Since you can trigger tern-mode, etc manually. I just prefer to have it automatic:
    ;;; Tern for Javascript
    ;;; http://truongtx.me/2014/04/20/emacs-javascript-completion-and-refactoring/
    ;;; npm install -g tern-autocomplete
    ;;; Tern with Nodejs' Express
    ;;; https://github.com/angelozerr/tern-node-express
    ;;; npm install -g tern-node-express
    (add-hook 'js-mode-hook (lambda () (tern-mode t)))
    (eval-after-load 'tern
      '(progn
         (require 'tern-auto-complete)
         (tern-ac-setup)))
    
    ;;; Start tern-mode automatically when starting js mode
    (add-hook 'js-mode-hook (lambda () (tern-mode t)))
    
Now we are ready!. Open a js file and you should get autocompletions right away yay!

Working with version control systems

Every time ternjs server starts it will create .tern-port file that contains the port number clients should access. This is a random number and it is recreated automatically. So, unlike .tern-project, .tern-port should NOT be committed.
If you are working with git, add this to .gitignore:
# Ignore ternjs port number
.tern-port

2015/06/07

CloudantDB

NoSQL系のDBが増えてきて最近 CloudantDB触る機会があって面白いと思ったのでここでメモを書くことにした。 まだまだわからないことだらけなのでこれをまたアップデートしていきたい。

CloudantDBとSQL系DBの違い

  • SQL系:
    一つのDBの中に複数のテーブルがありえる。テーブルの中にデータがある。テーブルにはスキーマが必須なのでデータに型がある。 検索するにはSQL Queryで書く
  • CloudantDB:
    一つのDBの中に沢山のJSONドキュメントがある。スキーマがない。それぞれのドキュメントにはJSONである以外の制約がないからドキュメントごと全く異なる情報を持つことができる。 ドキュメントIDで検索することができるがsqlクエリみたいなものを実現するにはsearch indexを事前に作る必要がある。クエリーはCloudant Queryといい Lucene Queryベースだそうである。

登録

まず登録しアカウント名とパスワードを取得し試しに幾つかのDBを作る。


簡単なAPIを使う

簡単の為にターミナルでcurlを使う。そして、ユーザー認証が必要だから次の変数を定義する。(本当はcurlの-uオプションで自分のパスワードを入力した方がbashのhistoryに残らなくてセキュリティ的にいいけどここでは最もシンプルな方法で紹介することにする)
CLOUDANT='https://アカウント名:パスワード@アカウント名.cloudant.com'
  • 😵 curlの復習:
    curl <url> \
        -X <method> \
        -H <header> \
        -d <data>
    
    *メソッドを指定しない場合 通常はGETとなる。

DB一覧を取得

これは一番簡単なAPIで上記のアカウント名とパスワードを試せる。
curl ${CLOUDANT}/_all_dbs
["_users","big","myanswers"]

DBを作成

car_answersというDBを用意する。 Cloudantのコンソルを使って5つのドキュメントを作ることができるが、ここでAPIで行う
curl ${CLOUDANT}/car_answers -X PUT
{"ok":true}

複数のドキュメントをアップロード

上記で作成したDBに次の5つのドキュメントをアップロードする。バルクというAPIを使うために一つのオブジェクトにdocsというキーにドキュメントの配列を定義する必要がある。ここでは_idを自分で指定しているけれども これは後で検索するから見やすさのためだけである。指定しなくてよい。Cloudantがユニークなものを生成してくれて それらをリスポンスに返ってくる。
json='{
    "docs": [
        {
            "_id": "1",
            "created_at": "2015-05-15 00:00:00",
            "maker": "Mercedes Benz",
            "モデル": "A 180",
            "answers": {
                "評価": "★★★",
                "その他": "特になし"
            }
        },
        {
          "_id": "2",
          "created_at": "2015-05-16 00:00:00",
          "maker": "Toyota",
          "モデル": "レクサス",
          "answers": {
              "評価":"★★★★",
              "その他":"よかった"    
          }
        },
        {
          "_id": "3",
          "created_at": "2015-05-17 00:00:00",
          "maker": "Honda",
          "モデル": "フィット",
          "answers": {
              "評価":"★★★★★",
              "その他":"すぐ購入したいと思います"    
          }
        },
        {
          "_id": "4",
          "created_at": "2015-05-18 00:00:00",
          "maker": "Mercedes Benz",
          "モデル": "E 250 CABRIOLET",
          "answers": {
              "評価":"★★",
              "その他":"イメージしていたものと少し違った"    
          }
        },
        {
          "_id": "5",
          "other": "他とちがうもの"
        }
    ]
}'
そして、この大きなオブジェクトをリクエストのボディーに入れて送信
curl ${CLOUDANT}/car_answers/_bulk_docs -X POST -H "Content-Type: application/json" -d "$json"
[{"id":"1","rev":"1-a408ef0c09d5b0aaec96b87e04e049de"},{"id":"2","rev":"1-615734de2db3b4717544f2dfd0e01bec"},{"id":"3","rev":"1-fd7d17addf8149d0c522368e69bda27c"},{"id":"4","rev":"1-d0be948351c84706c5febea1a1622700"},{"id":"5","rev":"1-4d8829cf3843c4d7a0b623bcced3a620"}]

単品でドキュメントをアップロード

バルクではなく、単品でドキュメントをアップロードもちろん可能。バルクと同じくidrevを指定しなくてよくてCloudantはユニークなものを生成してくれて 生成されたidrev番号がリスポンスに返ってくる。
json='{
  "created_at": "2016-07-14 00:00:00",
  "maker": "Android",
  "モデル": "N",
  "answers": {
    "評価": "★★★★★",
    "その他": "特になし"
  }
}'
curl ${CLOUDANT}/car_answers/ -X POST -H "Content-Type: application/json" -d "$json"
{"ok":true,"id":"c4d9e99028d5b4016ef008768bfee9a6","rev":"1-8e040f92ceea84466d2a90b5507f93b1"}

ドキュメント一覧を取得

例えば、car_answersというDBにある全ドキュメントを取得するには_all_docsを使って取れる
curl ${CLOUDANT}/car_answers/_all_docs
{"total_rows":5,"offset":0,"rows":[
{"id":"1","key":"1","value":{"rev":"1-a408ef0c09d5b0aaec96b87e04e049de"}},
{"id":"2","key":"2","value":{"rev":"1-615734de2db3b4717544f2dfd0e01bec"}},
{"id":"3","key":"3","value":{"rev":"1-fd7d17addf8149d0c522368e69bda27c"}},
{"id":"4","key":"4","value":{"rev":"1-d0be948351c84706c5febea1a1622700"}},
{"id":"5","key":"5","value":{"rev":"1-4d8829cf3843c4d7a0b623bcced3a620"}}
]}
デフォルトではリスポンスのrowsにドキュメントのidだけ渡される。ドキュメントその物を含んで欲しいときはinclude_docsフラグが便利
curl ${CLOUDANT}/car_answers/_all_docs?include_docs=true
デフォルトでは一つのリクエストで最大取得可能なドキュメントの数は25だが、それをlimitで200まで変更可
curl ${CLOUDANT}/car_answers/_all_docs?include_docs=true&limit=200
ドキュメント取得の詳細:docs.cloudant.com/database.html#get-documents

特定なドキュメントを取得

ドキュメントのidがわかれば そのドキュメントを簡単に取得できる。例えば、car_answersidが3のドキュメントを取る:
curl ${CLOUDANT}/car_answers/3
{"_id":"3","_rev":"1-fd7d17addf8149d0c522368e69bda27c","created_at":"2015-05-17 00:00:00","maker":"Honda","モデル":"フィット","answers":{"評価":"★★★★★","その他":"すぐ購入したいと思います"}}
条件を指定してドキュメントの取得は次に説明する。

ドキュメントを条件を指定して検索

CloudantのDBを検索するには検索インデックス(略してインデックス)が必要。

インデックスについて

インデックスとは

条件を指定して検索を実現するのは検索インデックスである。DBに入っているドキュメントはどんなものかCloudantにはわからない、まして、全ドキュメントは同じフォーマットである保証もない。キーワードで検索したいときに ドキュメントのどのフィルドをどのように扱えばいいかを決めるのは検索インデックスである。検索インデックスは必ずデザインドキュメントの中にあり、デザインドキュメントに複数の検索インデックスが存在しえる。

インデックスの作成

他のドキュメントと同じくAPIで作成可能だが 今回はCloudantDBのコンソルで作成する。car_answersというDBを選択しAll Design Docsの➕ボタンを選択しNew Search Indexを選ぶ。そしてCreate Search Indexという画面が表示される。


Create Search Indexの入力フィルドの説明
  • Save to Design Document
    どこのデザインドキュメントに保存するかを選択できるプルダウンメニューがあるが、一個もないときにデザインドキュメント作成用の入力フィルドも表示される。全てのデザインドキュメントが"_design/"で始まるのでそれは固定となっている
  • Index name
    検索インデックスの名前
  • Search index function
    これはインデックス化するに使う関数。入力としてはドキュメントが渡される。出力はない。インデックス化したいドキュメントのパラメーターをindex(...)でインデックスに入れることが役割。極普通のjavascriptの関数。使い方についてはこのあと説明する。
  • Analyzer
    インデックスに入れたテキスト(この場合はdoc.maker)をどういう風に区切って扱うかを決めるのはアナライザーだ。多分Tokenizerという言い方をする方がすぐわかりやすい
    例えばドキュメント1を処理した場合は"Mercedes Benz"というストリングがインデックスされる。"Mercedes Benz"を一つのストリングとして扱う("Keyword")か英語の文法を使って単語で分けるか日本語の文法で分けるかUnicode Text Segmentationアルゴリズム("Standard")で分けるかなど。アナライザーによって検索でドキュメントがヒットするかしないか変わる。より詳細は[Cloudant/For Developers/Search Indexes/Analyzers]を参照。
    関数ごとに一個のアナライザーを使う場合はSingle。インデックス化されるパラメーターごとにアナライザーを使う場合はMultipleを選択。

ドキュメントの検索・初級編

Analyzerはstandardでsingleな次のインデックスを作る(上記の画像と同じインデックス)
// インデックス名:car
function (doc) {
    index("brand", doc.maker);
}

curl ${CLOUDANT}/car_answers/_design/search/_search/car?q=brand:Honda
{"total_rows":1,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI4LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAmJgAAAAYn____9qaAJGP9OjegAAAABhAGo","rows":[{"id":"3","order":[0.3068528175354004,0],"fields":{}}]}
URL部意味
car_answersDBの名前
_design/searchデザインドキュメントのID
_search検索時に使う決まり
car検索インデックスの名前
qCloudantQuery用のパラメーター
brand:"Honda"CloudantQueryそのもの

他に試してみる
curl ${CLOUDANT}/car_answers/_design/search/_search/car?q=brand:Mercedes%20Benz
{"total_rows":2,"bookmark":"g1AAAACOeJzLYWBgYMpgTmGQS0lKzi9KdUhJMtXLTMo1MLDUS87JL01JzCvRy0styQGpy2MBkgwNQOr____zszKY3OznnJMDiSUyoJphQcCMBxAz_qOakQUAaqAqIg","rows":[{"id":"1","order":[0.028130024671554565,0],"fields":{}},{"id":"4","order":[0.028130024671554565,0],"fields":{}}]}
見つかった!ドキュメント1と4が返ってきている!
_searchでもinclude_docsオプションも使えるしlimitも使える。しかしbashなので&などをエンコードしないといけないしその次にURLエンコードしないといけないしまだ使っていないけどCloudantQueryの中でエスケープしないといけないものもあるのでこれから-vオプションを使う。実際に行われたリクエストがわかるから。
curl -v ${CLOUDANT}/car_answers/_design/search/_search/car?q=brand:Mercedes%20Benz\&limit=1\&include_docs=true
> GET /car_answers/_design/search/_search/car?q=brand:Mercedes%20Benz&limit=1&include_docs=true HTTP/1.1
...
{"total_rows":2,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI1LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAm4EAAAAAIBuBAD___-famgCRj-czh4AAAAAYQBq","rows":[{"id":"1","order":[0.028130024671554565,0],"fields":{},"doc":{"_id":"1","_rev":"1-a408ef0c09d5b0aaec96b87e04e049de","created_at":"2015-05-15 00:00:00","maker":"Mercedes Benz","モデル":"A 180","answers":{"評価":"★★★","その他":"特になし"}}}]}
定義された検索インデックス関数ではこれ以上大したものができないのでもうすこし拡張していく。

ドキュメントを検索・中級編

同じデザインドキュメントの中で新しい検索インデックスcar2を作成。同じくAnalyzerをstandardにする。
// インデックス名:car2
function (doc) {
    
    if (typeof doc.maker === 'undefined') {
        // makerというフィルドをもっていないドキュメントを相手にしない. 
        //(なんだかの理由でmakerをもっているもっていないドキュメントがある)
        return;
    }
    
    if (doc.created_at) {
        // 日付で検索したい。例: q=created_at:2015-09-25 11:30:58 ただし日付の部分をエスケープが必要
        index("created_at", doc.created_at, {"store": true});

        // 日付で楽に検索したい。例: q=20150925113058
        index("created", parseInt(doc.created_at.replace(/[^0-9]/g, ''), 10), {"store": true});
    }
    
    if (doc["モデル"]) {
        // 日本語キーで検索は可能だがエスケープが必要。これで楽にできる。「モデル」の代わりに「model」で検索可
        index("model", doc["モデル"], {"store": true});
    }

    if (doc.answers && doc.answers["評価"]) {
        // 評価で検索したい。例:q=stars:★★★ ただし★をエスケープしなければならない
        var stars = doc.answers["評価"];
        index("stars", stars, {"store": true});

        // 評価で楽に検索したい。例:q=stars_num:3
        index("stars_num", (stars.match(/★/g) || []).length);
    }

    if (doc.answers["その他"]) {
        // コメントなどで検索したい
        index("comment", doc.answers["その他"], {"store": true, "index": false});
    }
}
注意点
Javascriptの標準の挙動だが、関数のどっかでExceptionが発生するとそれ以降は実行されなくて インデックス化されないので 検索でヒットしないことがある。関数のよくチェックすること。

index関数の第三パラメーターについて

詳細は[Cloudant/For Developers/Search Indexes/Options]に書かれてあるけど、まとめると次のようになる
キー 意味 デフォルト
store 検索結果に値を含むかどうか。 false
index 検索インデックスに入れるかどうか。 true
facet faceting機能をオンにするかどうか。今回は使わない false
boost 検索結果の中で優先度を上げる係数。今回は使わない 1.0

いろいろと検索してみる

まずは({"store": true}を付けた)モデルで検索すると
curl -v ${CLOUDANT}/car_answers/_design/search/_search/car2?q=model:CABRIOLET
> GET /car_answers/_design/search/_search/car2?q=model:CABRIOLET HTTP/1.1
...
{"total_rows":1,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI4LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAm4EAAAAAOBuBAD_____amgCRj_Do3oAAAAAYQBq","rows":[{"id":"4","order":[0.1534264087677002,0],"fields":{"stars":"★★","model":"E 250 CABRIOLET","comment":"イメージしていたものと少し違った","created_at":"2015-05-18 00:00:00"}}]}
今までと違ってより詳細な情報を取得していることがわかる。それはstore:trueのお陰。これでinclude_docsを使わずに欲しい情報だけを取ることができる。
CloudantDBのコンソルで インデックスを簡単に試すことができる。URL全部書く必要がなくて Cloudant Queryの部分のみ、いろいろ試すには便利だね
エスケープについては便利かどうかを試したい。
おなじく
curl ${CLOUDANT}/car_answers/_design/search/_search/car2?q=stars_num:2
{"total_rows":1,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI3LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAm4EAAAAAOBuBAD_____amgCRj_wAAAAAAAAYQBq","rows":[{"id":"4","order":[1.0,0],"fields":{"stars":"★★","model":"E 250 CABRIOLET","comment":"イメージしていたものと少し違った","created_at":"2015-05-18 00:00:00","created":20150518000000.0}}]}

Official docsメモ
The following characters require escaping if you want to search on them;
+ - && || ! ( ) { } [ ] ^ " ~ * ? : \ /
Escape these with a preceding backslash character. https://cloudant.com/for-developers/search/

日本語で検索

ここからエスケープがかなりわけがわからなくなるのでcurlの--data-urlencodeオプションでクエリエンコーディングをする。そして-Gオプションで urlに付ける。さらに-vでverboseをオンにして実際に叩かれたURLの確認ができる。
model:"フィット"の検索はこんな感じ
curl -v -G ${CLOUDANT}/car_answers/_design/search/_search/car2 --data-urlencode 'q=model:"フィット"'
> GET /car_answers/_design/search/_search/car2?q=model%3A%22%E3%83%95%E3%82%A3%E3%83%83%E3%83%88%22 HTTP/1.1
...
{"total_rows":1,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI3LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAmJgAAAAYn____9qaAJGP9OjegAAAABhAGo","rows":[{"id":"3","order":[0.3068528175354004,0],"fields":{"stars":"★★★★★","model":"フィット","comment":"すぐ購入したいと思います","created_at2":"2015-05-1700:00:00","created_at":"2015-05-17 00:00:00","created":20150517000000.0}}]}
stars:"★★"の検索は次の通りのはずだがうまくいかない。
curl -v -G ${CLOUDANT}/car_answers/_design/search/_search/car2 --data-urlencode 'q=stars:"★★"'
> GET /car_answers/_design/search/_search/car2?q=stars%3A%22%E2%98%85%E2%98%85%22 HTTP/1.1

{"total_rows":0,"bookmark":"g2o","rows":[]}
その理由はAnalyserのstandardにある。AnalyzerについてCloudant Analizersにの述べているように standardで"★★"というストリングをトークン化しようとするとトークンの数がゼロなのでstandardで記号を決してヒットしない。別のインデックスを作っておく必要がある。この場合はkeywordもしくはwhitespaceがいいかと思う。

レンジ検索・数字編

数字は割りと簡単で問題なし
curl -v -G ${CLOUDANT}/car_answers/_design/search/_search/car2 --data-urlencode 'q=created:[20150501000000 TO 20160705235959]'
> GET /car_answers/_design/search/_search/car2?q=created%3A%5B20150501000000%20TO%2020160705235959%5D HTTP/1.1

{"total_rows":4,"bookmark":"g1AAAAELeJzLYWBgYMlgTmGQS0lKzi9KdUhJMtHLTMo1MLDUS87JL01JzCvRy0styQGqY0pkSJL___9_VgaTm_0HBjBIZEDVbIZTc1ICkEyqJ6Aft-V5LCD1DUAKaMR88twAMeMBxAw0d2QBADHUTsY","rows":[{"id":"2","order":[1.0,0],"fields":{"stars":"★★★★","model":"レクサス","stars2":4.0,"comment":"よかった","created_at":"2015-05-16 00:00:00","created":20150516000000.0}},{"id":"3","order":[1.0,0],"fields":{"stars":"★★★★★","model":"フィット","stars2":5.0,"comment":"すぐ購入したいと思います","created_at":"2015-05-17 00:00:00","created":20150517000000.0}},{"id":"1","order":[1.0,0],"fields":{"stars":"★★★","model":"A 180","stars2":3.0,"comment":"特になし","created_at":"2015-05-15 00:00:00","created":20150515000000.0}},{"id":"4","order":[1.0,0],"fields":{"stars":"★★","model":"E 250 CABRIOLET","stars2":2.0,"comment":"イメージしていたものと少し違った","created_at":"2015-05-18 00:00:00","created":20150518000000.0}}]}

レンジ検索・ストリング編

この辺になると またエスケープがややこしすぎてわけわからなすぎる。検索したいのは2015-05-01 00:00:00ストリングなので"で囲む。内容の:-をエスケープが必要。しかし 上記と同じレンジを指定しているのに結果が違う。なぜだろう。スペースが怪しいと思う。
結果が違う理由はAnalizerのstandardによってトークン化された結果が違うからだ。数字をトークン化しようとすると同じ数字になるけど、数字でない場合はスペースやさまざまな記号で別れたりする。
この場合は完全マッチの検索に近いからkeywordwhitespaceがいいかもしれないね。AnalyzerについてCloudant Analizersに書いた。 とりあえず有効なqueryのようだが 期待しているものではない
curl -v -G ${CLOUDANT}/car_answers/_design/search/_search/car2 --data-urlencode 'q=created_at:["2015\-05\-01 00\:00\:00" TO "2016\-07\-05 23\:59\:59"]'
> GET /car_answers/_design/search/_search/car2?q=created_at%3A%5B%222015%5C-05%5C-01%2000%5C%3A00%5C%3A00%22%20TO%20%222016%5C-07%5C-05%2023%5C%3A59%5C%3A59%22%5D HTTP/1.1

{"total_rows":2,"bookmark":"g1AAAACPeJzLYWBgYMpgTmGQT0lKzi9KdUhJMjTRy0zKNTCw1EvOyS9NScwr0ctLLckBKcxjAZIMC4DU____92dlMLnZf2AAg0QGkCFycEPMCZjxAGLGfxQzGLMAarcpYw","rows":[{"id":"7fbfa0bbc27b8ab664d64cde5d00b421","order":[1.0,0],"fields":{"stars":"★★★★★","model":"N","stars2":5.0,"comment":"特になし","created_at":"2016-07-14 00:00:00","created":20160714000000.0}},{"id":"c4d9e99028d5b4016ef008768bfee9a6","order":[1.0,1],"fields":{"stars":"★★★★★","model":"N","stars2":5.0,"comment":"特になし","created_at":"2016-07-14 00:00:00","created":20160714000000.0}}]}

ドキュメントの検索・上中級(TODO)

* Multiple Analizersを持つインデックスでの検索("★"などの記号で検索したい)
* 検索: index:falseなどについて(検索キーワードとして使いたくないけど(store:trueに合わせて使うと)と結果に出て欲しいとき)
* 検索: ranges, [], (), AND, OR, などについて

CloudantQueryでエスケープについて

StackoverflowのEscaping Lucene Characters using Javascript Regexの回答で大抵カバーできるけど、下記では\と/とも対応している。
function luceneEscaped(input) {
    // http://stackoverflow.com/questions/26431958/ + own additions
    // + - && || ! ( ) { } [ ] ^ " ~ * ? : \ /
    input = input.replace(/([\!\*\+\&\|\(\)\[\]\{\}\^\~\?\:\"\-\\\\\/])/g, "\\$1")
    return '"' + input + '"';
}

2015/05/17

Cloudant basics

Find a document by id: I want to look for an design document which name is 'search'. So the id of the document would be '_design/search' Search the document using the primary index with a parameter:
$ curl -u accountame:password 'accountname.cloudant.com/qdef/_all_docs?key="_design/search"&include_docs=true'
{"total_rows":5,"offset":3,"rows":[
{"id":"_design/search","key":"_design/search","value":{"rev":"14-1826c1455da23e06b926e9a29ac9ec63"},"doc":{"_id":"_design/search","_rev":"14-1826c1455da23e06b926e9a29ac9ec63","views":{"names":{"map":"function(doc) {...}"}}}}
]}
Search the document with its id directly:
$ curl -u accountame:password 'accountname.cloudant.com/qdef/_design/search'

{"_id":"_design/search","_rev":"14-1826c1455da23e06b926e9a29ac9ec63","views":{"names":{"map":"function(doc) {...}"}},"language":"javascript","indexes":{"doc":{"analyzer":"standard","index":"function (doc) {...}"}}}

Useful Links

Lucene stuff
http://lucene.apache.org/core/4_3_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Overview
Escaping Lucene Characters using Javascript Regex
http://stackoverflow.com/questions/26431958/escaping-lucene-characters-using-javascript-regex
CouchDB API documentation (Basis of Cloudant)
http://wiki.apache.org/couchdb/HTTP_Document_API
View collation (selecting stuff using views)
http://docs.couchdb.org/en/latest/couchapp/views/collation.html
Cloudant library for nodejs on Github
https://github.com/cloudant/nodejs-cloudant#Cloudantdbgetname-callback
Search with complex keys (this is too advanced for me probably)
https://developer.ibm.com/answers/questions/173495/bluemix-cloudant-nano-how-to-search-on-complex-arr.html
Find documents within a range (me)
https://developer.ibm.com/answers/questions/191004/how-to-search-a-string-whose-value-is-between-2015.html
URL encoder/decoder - Useful tool for working with non ascii strings in below tutorials
http://meyerweb.com/eric/tools/dencoder/

Official IBM Cloudant sites

Cloudant API Documentation. (With very simple examples at the right)
https://docs.cloudant.com/api.html#index-functions
Cloudant.com - Primary Indexes (_all_docs)
https://cloudant.com/for-developers/all_docs/
Cloudant.com - Secondary Indexes (views)
https://cloudant.com/for-developers/views/
Cloudant.com - Search Indexes (Queries)
https://cloudant.com/for-developers/search/
Cloudant.com - Examples. Full Text indexing is quite easy to understand
https://cloudant.com/for-developers/examples/
https://cloudant.com/blog/search-faceting-from-scratch-2/
Cloudant.com - Authentication
https://cloudant.com/for-developers/faq/auth/
Cloudant.com - Introducing Cloudant Query
https://cloudant.com/blog/introducing-cloudant-query/

2015/04/06

Curl + Charles

As this blog post, when using Charles for debugging requests done by cURL:

When using libCurl (programmatically):

Set CURLOPT_PROXY and CURLOPT_PROXYPORT as below:
curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1");
curl_setopt($ch, CURLOPT_PROXYPORT, 8888);

// And to avoid certificate trust errors...
// WARNING: Do not use this in production environments
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);

When using curl from the terminal

Option --proxy or its short for -x can be used
curl --proxy 127.0.0.1:8888 'http://my.host.com/api/path?param=value'
For example:
curl \
  -X GET \
  -H 'Authorization: Basic MYTOKEN' \
  --proxy '127.0.0.1:8888' \
  -v \
  'http://my.host.com/api/path?param=value'

2015/03/22

Software development is an engineering discipline

I don't usually post to write thoughts but very objective and technical things however this time is kind of an exception.

On O'reilly Software Architecture - Glenn Vanderburg of LivingSocial on why software development is an engineering discipline.

I couldn't agree more.
Maths don't define an engineering, they are there to economically aid or demonstrate a particular hypothesis. They were introduced after the engineering itself. In Software development it is (economically and timely speaking) cheap to build the product so demonstrating things with maths is not really a must

Software engineering is pragmatic and iterative as many other engineering science. However it is immature.

This cartoon and what follows right after it is what I think is the best moment in the session.


Why is this so funny? It is funny because it is absurd. No body will ever do that.

Why is this so funny? It is funny because it is absurd - No body will ever do that.

Yeah, software developers will never do that ...

2015/02/11

Swift stuff

I started with Swift recently. So this is going to be the place I put all the gotchas, tricks and everything including complains
  • Private functions are private:
    This:
    self.addTarget(self, action: Selector("valueChanged"), forControlEvents:.ValueChanged)
    
    private func valueChanged() { ... }
    
    Leads to: unrecognized selector sent to instance the function must not be private.
    func valueChanged() { ... }
    
  • Downcasting:
    These two are basically equal. (cell is an UITableCellView). In objc there is no problem accessing nil stuff. It is a NOP, has good and bad sides. In swift it must be explicitly declared and accessing nils leads to crashes.
    // objc
    [[cell viewWithTag:102] setText:@”Something”];
    
    //or something syntaxly closer to below swift code
    ((UILabel *)[cell viewWithTag:102]).text = @”Something”;
    
    // swift
    (cell.viewWithTag(102) as? UILabel)?.text = “Something”
    
    // to refer `label` more than once create a variable and access its methods if not nil
    if let label = cell.viewWithTag(102) as? UILabel {
        label.text = “Something”
    }
    

2015/02/03

Change computer name

Just after installing a new version of OS X you just realized you don't like the name of your computer?. No problem just go to Preferences > Sharing and change it:

But then you go to Terminal.app and realized that your prompt still has the old name :(. That is because the default terminal prompt uses the hostname. Doing above things does not change the hostname.

You could change the prompt by modifying $PS1 variable but that will not solve the problem as the kernel hostname would still have the old name.

Change the Hostname

To change it we use sysctl kern.hostname=NEW_NAME
$ hostname
Ignacio-no-MacBook-Pro.local

$ sudo sysctl kern.hostname=IgnacioMBP
kern.hostname: Ignacio-no-MacBook-Pro.local -> IgnacioMBP

$ hostname
Ignacio-MBP.local
To confirm current value we use hostname command. Now we have to restart the terminal so the new hostname is read again populating $PS1 as intended :)


See also:
MDLog:/sysadmin - How to change the hostname of a Linux system

2015/01/17

Setup Go in Windows and Mac

Some notes on how to setup the computer to get started with go language. Very simple notes. Installation notes for Mac are at the end

On Windows

  1. Installed it via the msi installer from the downloads page
  2. Open the command line (cmd.exe) and check everything is fine
    > go version
    go version go1.4 windows/386
    Go installer sets up GOROOT (although I think it is not needed to define it explicitly since the go tool can find it)
    > echo %GOROOT%
    C:\Go\
    
    We can also check other variables
    > go env GOOS
    windows
    
    > go env GOARCH
    386
    

  3. The only environment variable that needs to be set is GOPATH

    Add GOPATH as a new User Environment Variable, its value should be somewhere we are going to put all our go code, also it should not be the same as GOROOT .
    > echo %GOPATH%
    C:\Users\GuillermoIgnacio\Documents\go
    
    As explained in the docs, GOPATH directory needs to have 3 directories in it: src, pkg and bin
    cd %GOPATH%
    mkdir src
    mkdir pkg
    mkdir bin
    
  4. Optionally, we could add GOPATH\bin to our PATH so we can run executables easier after each time we use the install command: go install something

On Mac OS X

  1. Install it via homebrew
    brew install go
    
  2. Create a workspace. (The workspace is a directory with 3 subdirectories src, pkg and bin) so we can set $GOPATH variable in the next step. I created inside ~/Documents directory
    cd ~/Documents
    mkdir goworkspace
    cd goworkspace
    mkdir src
    mkdir pkg
    mkdir bin
    
  3. Set $GOPATH in ~/.bash_profile. Optionally we can add bin
    # Go stuff                                                                                                          
    export GOPATH="${HOME}/Documents/goworkspace"
    export PATH=$PATH:"${GOPATH}/bin"
    
  4. Also get go vet and go doc
    go get golang.org/x/tools/cmd/vet
    go get golang.org/x/tools/cmd/godoc
    
Done!. Now I am ready to start with hello world or a little library :)

2015/01/12

Cleaning my Mac

The other day I spent like 4 hours removing unneeded stuff from my PC. It took me this long because I had several old files like unused VirtuaBox kernel extensions (I remember installing them when trying to learn some WebOS programming), numerous useless plugins all over the computer, other kinds of extensions, trash, etc. I googled each one of them to make sure I don't delete important things by mistake.
This is a list of things/places to check so in the future I don't have to google everything again. :)
  1. First, EtreCheck is a tool I like it very much because is not offensive or intrusive as Onyx or similar tools, unlike CleanMyMac 2 it has no ads (CleanMyMac is crap). EtreCheck is free, relatively fast and open source.
    EtreCheck attempts to just report the facts, without making any judgements or recommendations. Debugging is not a task that can be automated. It needs humans, ...
    Just run the app and it will tell what extensions, plugins, etc is loaded in the system. These are good places to start to look for.

  2. Login items Go to Preferences > Users & Groups
    Delete all unneeded items from from the list. Example, Dropbox, Google drive. All those things I don't use anymore. I only use OneDrive now :).

    Also read this: Stackexchange - What is the yellow warning sign in the login items tab....

  3. Daemons and/or agents.
    Look for things like google talk, Adobe ancient stuff, etc. Legacy stuff (prior to 10.4)
    /Library/StartupItems
    /System/Library/StartupItems
    
    Items loaded at Mac starts up time. Run as the root user.
    /Library/LaunchDaemons
    /System/Library/LaunchDaemons
    
    Items loaded when any user logs in. Run as that user.
    /Library/LaunchAgents
    /System/Library/LaunchAgents
    
    Items loaded only when ${USER} user logs in. Run as that user.
    ~/Library/LaunchAgents
    
    Useful commands:
    $ launchctl list
    $ launchctl list | grep -v "com.apple"
    
  4. Kernel extensions This places are quite sensitive. Make sure you don't delete unwanted things from here of the entire OS might break.
    /System/Library/Extensions
    /Library/Extensions
    ~/Library/Extensions
    
    Cnet - Increasing system stability by pruning the kernel extension folder

  5. Internet plugins
    Make sure you have read this article:Unsupported third-party add-ons may cause Safari to unexpectedly quit or have performance issues and don't delete Apple stuff incorrectly.
    /Library/Internet Plug-Ins/
    /Library/Input Methods/
    /Library/InputManagers/
    /Library/ScriptingAdditions
    ~/Library/Internet Plug-Ins/
    ~/Library/Input Methods/
    ~/Library/InputManagers/
    ~/Library/ScriptingAdditions
    

2014/09/30

SIMBL Plugins

What is SIMBL ?

As the wiki says, SIMBL is an application enhancement (InputManager bundle) loader for Mac OS X developed by Mike Solomon. Now a days SIMBL is not maintained anymore but there is a new version of it called EasySIMBL, maintained by Nomura-san.
In short, it is a plugin loader for any Cocoa Application in OSX. So, If you have an app you want to hack (lets say add some extra functionality) to an App and that app does not brings Plugin functionality then EasySIMBL is your option.

First Steps

  1. Download and Install EasySIMBL.
  2. Create an Xcode project
    File > New > Project > OS X > Framework & Library > Bundle
  3. Follow further instructions from "Creating A SIMBL Plugin Bundle" from old SIMBL wiki page.
    Basically, make sure your Info.plist have SIMBLTargetApplications with appropriate values in BundleIdentifier, MaxBundleVersion and MinBundleVersion.

    For the more curious, there is an undocumented parameter: RequiredFrameworks. See details here. It says it has never been used but I personally think it could be useful in cases when your plugin requires a non-standard framework which is embedded in the target app. Using this key load your plugin only if the required frameworks exist.
  4. Start by implement load method as explained in the old wiki.
  5. Once you build your plugin you can move it manually onto EasySIMBL and set the Debug level to "Notice + Info + Debug" to see a bit more of information about the loading process of your plugin
  6. Open Console.app and see "All Messages" now open the target App and check what happens...

Debug

Obviously the process of manually copying the .bundle to ~/Library/Application\ Support/SIMBL/Plugins is tedious. We can do it a bit better:

# Copy the product to SIMBL plugins dir
SIMBL_PLUGIN_DIR="${HOME}/Library/Application Support/SIMBL/Plugins"
rm -rf "${SIMBL_PLUGIN_DIR}/${FULL_PRODUCT_NAME}"
cp -r "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}" "${SIMBL_PLUGIN_DIR}/${FULL_PRODUCT_NAME}"

However, in the end this is not what we want. We want to be able to run the target app with the plugin and be able to set breakpoints, etc!.

Setting Xcode for plugin development

This step is not specific to EasySIMBL. Basically every plugin in OSX can be developed/debbuged in this way:
  1. Tell Xcode where to place product after build.
    In our case the location is: "~/Library/Application Support/SIMBL/Plugins". Do this by editing CONFIGURATION_BUILD_DIR on your target:
    .
    Note that some apps (that support plugins without the need of EasySIMBL) will require the plugin to be in ~/Library/Application Support/TheApp/Plugins/ or sometimes in /Applications/TheApp/Contents/Plugins. Just make sure you have enough access permissions so Xcode can write there (Hint: use chmod if needed).

    For example iPhoto.app
  2. Tell Xcode what application to start on "Run".
    In my case I just created a example plugin for Writer.App (That awesome app!). So I edit my schema. Product > Scheme > Edit Scheme...
  3. Specific to EasySIMBL. EasySIMBL seems not to load correctly the latest version of the plugin on Sandboxed apps. Nomura-san says he is aware of this issue and it can be work-arounded by un-checking and checking the "Use SIMBL" before each time we run our plugin.
  4. This is basically all. This is my 1 cent and comments are welcome :)

2014/08/08

Recent Posts Widget for Blogger

I got a little bored the standard "Blog Archive" widget. So I searched for a plain list of post on the widget site without luck but I found this useful helperblogger page which provided a script that uses below API to get the entire list of posts parse it and show it nicely.
GET https://www.blogger.com/feeds/blogID/posts/default
(Full documentation of Blogger's API can be found here)
So, what I did was to modify the code a bit for visual purposes and then brought it into my code. Just set below code as an "HTML/JS Widget":
<script type="text/javascript">
var numposts = 20;
var showpostdate = false;
var showpostsummary = false;
var numchars = 100;
function showrecentposts(json) {
    document.write('<ul>');
    for (var i = 0; i < numposts; i++) {
        var entry = json.feed.entry[i];
        var posttitle = entry.title.$t;
        var posturl;
        if (i == json.feed.entry.length) break;
        for (var k = 0; k < entry.link.length; k++) {
            if (entry.link[k].rel == 'alternate') {
                posturl = entry.link[k].href;
                break;
            }
        }
        posttitle = posttitle.link(posturl);
        var readmorelink = "»»";
        readmorelink = readmorelink.link(posturl);
        var postdate = entry.published.$t;
        var cdyear = postdate.substring(0, 4);
        var cdmonth = postdate.substring(5, 7);
        var cdday = postdate.substring(8, 10);
        var monthnames = new Array();
        if (showpostdate == true) {
            monthnames[1] = "Jan";
            monthnames[2] = "Feb";
            monthnames[3] = "Mar";
            monthnames[4] = "Apr";
            monthnames[5] = "May";
            monthnames[6] = "Jun";
            monthnames[7] = "Jul";
            monthnames[8] = "Aug";
            monthnames[9] = "Sep";
            monthnames[10] = "Oct";
            monthnames[11] = "Nov";
            monthnames[12] = "Dec";
        }
        if ("content" in entry) {
            var postcontent = entry.content.$t;
        } else if ("summary" in entry) {
            var postcontent = entry.summary.$t;
        } else {
            var postcontent = "";
        }
        var re = /<\S[^>]*>/g;
        postcontent = postcontent.replace(re, "");
        document.write('<li>');
        document.write(posttitle);
        if (showpostdate == true) {
            document.write(' - ' + cdday + ' ' + monthnames[parseInt(cdmonth, 10)] + ' ' + cdyear);
        }
        if (showpostsummary == true) {
            document.write('');
            if (postcontent.length < numchars) {
                document.write('<i>');
                document.write(postcontent);
                document.write('</i>');
            } else {
                document.write('<i>');
                postcontent = postcontent.substring(0, numchars);
                var quoteEnd = postcontent.lastIndexOf(" ");
                postcontent = postcontent.substring(0, quoteEnd);
                document.write(postcontent + '... ' + readmorelink);
                document.write('</i>');
            }
        }
        document.write('</li>');
    }
    document.write('</ul>');
}
</script>
<script src="http://nacho4d-nacho4d.blogspot.com/feeds/posts/default?orderby=published&alt=json-in-script&callback=showrecentposts">
</script>

My changes

  • Removed all custom ui modifications and put the post in a ul. I added a custom bullet by giving setting the ul an id #archive-list and adding its style in the template
    #archive-list {
     list-style: none;
     margin-left: 0;
     padding-left: 1em;
     text-indent: -1em;
     }
    #archive-list li:before {
     content: "\0BB \020";
     }
    
  • I thought of using feeds/posts/default instead of the full path http://nacho4d-nacho4d.blogspot.com/feeds/posts/default however it does not work on posts pages.