p-baleine / jq.el

Emacs Lisp bindings for jq.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

jq.el.

https://github.com/p-baleine/jq.el/workflows/CI/badge.svg?branch=master

Decription

Emacs Lisp bindings for jq.

Usage

(require 'jq)

(let ((input "
[
  {
    \"name\": \"Ness\",
    \"age\": 12,
    \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" }
  },
  {
    \"name\": \"Paula\",
    \"age\": 11,
    \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" }
  }
]
"))
  (cl-loop for x iter-by (jq input ".[] | .origin.town") collect x))

;;=> ("Onett" "Twoson")

Motivation

Recently I wanted an API client for Emacs which communicate with a RESTful server. I read documents and code of the RESTful server and designed the API client naively by calling the server with curl and jq.

Then, when I would implement the designed client on Emacs Lisp, I remembered that it is a bother to parse JSON strings on Emacs Lisp and I would have to call the server multiple times to implement the client.

Emacs Lisp includes json.el and can parse JSON strings but the results are association lists, so if the fields I am interested in are placed deeply inner the code to retrieve the fields would be very complicated.

Below is a sample where I would want origin.town of each element from a JSON string input.

(let* ((input "
[
  {
    \"name\": \"Ness\",
    \"age\": 12,
    \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" }
  },
  {
    \"name\": \"Paula\",
    \"age\": 11,
    \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" }
  }
]
")
       (parsed (json-read-from-string input)))
  (cl-loop for entry across parsed
    collect (let* ((origin (cdr (assoc 'origin entry)))
                   (town (cdr (assoc 'town origin))))
              town)))

;;=> ("Onett" "Twoson")

If my interested fields are placed more deeply inner, the program would be more complicated.

Why I cannot retrieve interested fields of JSON string as simple as the design phase where I could use curl and jq?

This is the motivation that drives me to implement jq.el. With jq.el, we can rewrite the above program as below.

(let ((input "
[
  {
    \"name\": \"Ness\",
    \"age\": 12,
    \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" }
  },
  {
    \"name\": \"Paula\",
    \"age\": 11,
    \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" }
  }
]
"))
  (cl-loop for x iter-by (jq input ".[] | .origin.town") collect x))

;;=> ("Onett" "Twoson")

Getting Started

Prerequisites

cmake
>= 3.10.0
other build dependencies
oniguruma headers, gcc, gcc-c++, libtool, autoconf, yacc & lex and git

To install all build dependencies on Ubuntu, use:

apt install cmake build-essential autoconf libtool bison flex

On Fedora, use:

dnf install cmake make gcc gcc-c++ autoconf libtool bison flex

Building

mkdir build
cd build
cmake ..
cmake --build .

Installing

On Doom Emacs

Add the following code to ~/.config/doom/packages.el.

(package! jq
  :recipe (:local-repo "/path/to/jq.el"
            :files (
              "*.el"
              "build/jq-impl.so")))

On Spacemacs

Add the following code to dotspacemacs-additional-packages of ~/.spacemacs.

dotspacemacs-additional-packages
'(
  ;; ...
  (jq.el :location "/path/to/jq.el/jq.el")
  (jq-impl.el :location "/path/to/jq.el/build/jq-impl.el")
  )

With Emacs Lisp Packages

Add the following code to ~/.init.el.

(require 'package)

(package-install-file "/path/to/jq.el/jq.el")
(package-install-file "/path/to/jq.el/build/jq-impl.el")

API

jq (input program)

Return results of executing jq by passing input and program as arguments.

Development

Running tests

emacs -Q --batch -L build -f batch-byte-compile jq.el \
  && emacs -Q --batch -L build -L . -l test/jq-test.el -f ert-run-tests-batch-and-exit

Acknowledgments

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

About

Emacs Lisp bindings for jq.

License:MIT License


Languages

Language:C++ 50.1%Language:Emacs Lisp 25.3%Language:CMake 18.5%Language:C 6.1%