惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

Maciej Walkowiak - Java & Spring

Blog Generating HTTP clients in Spring Boot application from OpenAPI spec PostgreSQL and UUID as primary key Dynamic Projections with Spring Data JPA Container logs with Spring Boot and Testcontainers Reified Generics in Java? Faster integration tests with reusable Testcontainers and Flyway Running one-time jobs with Quartz and Spring Boot The best way to use Testcontainers with Spring Boot Spring Boot & Flyway - clear database between integration tests What's new in Spring? Activate Maven Profile by Operating System Spring Boot with Thymeleaf and Tailwind CSS - Complete Guide How to publish a Java library to Maven Central - Complete Guide Docker Compose - waiting until containers are ready Single file Java applications with JBang Beautiful bash scripts with Gum Running Java on CRaC How to log PostgreSQL queries with Testcontainers Spring Boot 3.0 & GraalVM Native Image - not a free lunch Creating Spring Cloud Function projects with AWS SAM Loading classpath resources to String with a custom JUnit extension Creating Project Templates with Cookiecutter Auto-Registering JUnit 5 extensions Spring Boot component scanning without annotations Listing Maven dependencies in Spring Boot Actuator Info endpoint Spring Cloud AWS 2.3 RC2 Released The State of Java Relational Persistence On Choosing a Tech Stack
How I built vlad-cli - command line interface to Vlad Mihalcea
2020-05-07 · via Maciej Walkowiak - Java & Spring

TLDR; ​

Vlad Mihalcea's blog is a default place I go to when I struggle with Hibernate or JPA, which lead to the following tweet exchange:

Joke or not joke - building such CLI can be a quick and fun project, so why not?

Searching through the blog ​

The first question is how to search through the blog. None of the public search engines provide search API, there are some third party options that scrape Google results and return them as JSON - but first - as far as I can tell it violates Google terms of use and it's a paid API. Nothing wrong with paying for API but not for this kind of project.

Fortunately, Vlad's blog is based on Wordpress. Wordpress by default comes with a search API - not sure what is used under the hood and how good are the search results but for sure they are as good as search functionality on Vlad's blog - so we can assume it's good enough.

A blog can be searched with executing curl on search endpoint:

$ curl -s https://vladmihalcea.com/wp-json/wp/v2/search\?search\=how\ to\ map\ json

[
  {
    "id": 5905,
    "title": "How to map JSON objects using generic Hibernate Types",
    "url": "https://vladmihalcea.com/how-to-map-json-objects-using-generic-hibernate-types/",
    "type": "post",
    "subtype": "post",
    "_links": {
      "self": [
        {
          "embeddable": true,
          "href": "https://vladmihalcea.com/wp-json/wp/v2/posts/5905"
        }
      ],
      "about": [
        {
          "href": "https://vladmihalcea.com/wp-json/wp/v2/types/post"
        }
      ],
      "collection": [
        {
          "href": "https://vladmihalcea.com/wp-json/wp/v2/search"
        }
      ]
    }
  },
  ...
]

vlad-cli as a bash script ​

The initial idea was just to write a very simple bash script that will open the first search result in a browser.

With jq we can easily extract url from json returned by curl:

curl -s https://vladmihalcea.com/wp-json/wp/v2/search\?search\="how to map json" | jq -r ".[0].url?"  

https://vladmihalcea.com/how-to-map-json-objects-using-generic-hibernate-types/

jq is a super handy utility I totally recommend checking. ".[0].url?" translates into: extract url property from the first entry in array and do not fail if it's not present.

Adding -r strips quotes from the result.

The last thing is to pass the result to the command that opens a web browser and put it together in a bash script

#!/bin/sh
str="$*"
open $(curl -s https://vladmihalcea.com/wp-json/wp/v2/search\?search\="$str" | jq -r ".[0].url?")
exit;

str="$*" concatenates all arguments so that we can execute the script like:

$ ./vlad.sh how to map json

This script works, but has the following drawbacks:

  • open command works only on Mac - other OS have different commands that serve the same purpose
  • the first result is not necessarily the best
  • how to distribute bash script as an easily installable package?

I decided to address them by rewriting the script to NodeJS, mainly because most developers are used to installing packages through npm, there is a mindblowing number of available packages to use and it's freaking easy to publish package making it available to anyone to install.

Building clis with Node ​

Finding reliable CLI framework in Node ecosystem is quite straightforward - oclif - made by Heroku and used by Heroku to build their CLI (which is awesome). It's also used by Twillio. I don't really need more recommendations than that.

First, we need to create project:

npx oclif single vlad-cli

Then there are few usual questions to answer like who is the owner, what is the package name etc and finally if we want to build it with TypeScript or JavaScript.

? npm package name vlad-cli
? command bin name the CLI will export vlad-cli
? description 
? author Maciej Walkowiak @maciejwalkowiak
? version 0.0.0
? license MIT
? Who is the GitHub owner of repository (https://github.com/OWNER/repo) maciejwalkowiak
? What is the GitHub name of repository (https://github.com/owner/REPO) vlad-cli
? Select a package manager yarn
? TypeScript No
? Use eslint (linter for JavaScript and Typescript) No
? Use mocha (testing framework) No

Now we have application skeleton generated, we can already run it with ./bin/run

$ ./bin/run 
hello world from ./src/index.js

Passing the search query to the command ​

At this stage if we just want to pass search query to the command we will get the following output:

$ ./bin/run how to map json
 ›   Error: Unexpected arguments: how, to, map, json
 ›   See more help with --help

Each word is considered to be a separate argument and by default with oclif you have to explicitly list all available arguments.

We can disable this behavior by setting static strict = false on VladCliCommand. Then we can obtain an array of arguments from the result of parse:

js

class VladCliCommand extends Command {
  static strict = false

  async run() {
    const {argv} = this.parse(VladCliCommand)
    const query = argv.join(' ')
    // ...
  }
}

Fetching search results ​

Now we need to fetch search results from Wordpress search API. If you have worked with JavaScript at least a bit you are likely familiar with fetch function. It's not available in Node environment so we need to add a dependency:

... and fetch the results. We can use async/await methods if you don't want to break your neck with promises:

js

const fetch = require('node-fetch')

class VladCliCommand extends Command {
  static strict = false

  async run() {
    const {argv} = this.parse(VladCliCommand)
    const query = argv.join(' ')

    const searchResponse = await fetch(`https://vladmihalcea.com/wp-json/wp/v2/search?search=${query}`)
    const json = await searchResponse.json()

    // ...
  }
}

Prompting user to choose article ​

We are not going to open the first available search result, instead, we want to give the user option to choose one from the list.

There is no utility for that built into oclif but there is a fantastic JavaScript library that does it (and much more): Inquirer.js

js

const fetch = require('node-fetch')
const inquirer = require('inquirer')

class VladCliCommand extends Command {
  static strict = false

  async run() {
    const {flags, argv} = this.parse(VladCliCommand)
    const query = argv.join(' ')

    const searchResponse = await fetch(`https://vladmihalcea.com/wp-json/wp/v2/search?search=${query}`)
    const json = await searchResponse.json()

    let response = await inquirer.prompt([{
      name: 'stage',
      message: '🤔 Choose article',
      type: 'rawlist',
      choices: json.map(function (entry) {
        return {name: entry.title}
      })
    }])

    // ...
  }
}

This will give us a list of options that can be chosen with arrows or putting the number:

./bin/run how to map json
? 🤔 Choose article 
  1) How to map JSON objects using generic Hibernate Types
  2) How to map SQL Server JSON columns using JPA and Hibernate
  3) How to map Oracle JSON columns using JPA and Hibernate
  4) How to map a String JPA property to a JSON column using Hibernate
  5) How to map a JSON collection using JPA and Hibernate
  6) How to map a PostgreSQL ARRAY to a Java List with JPA and Hibernate
(Move up and down to reveal more choices)
  Answer:

The choice is saved in response.stage property so to get the url of a chosen article we need to filter previously returned json by comparing titles. Not the greatest solution since it would fail if two articles have the same title, but since it's a just-for-fun project and it's unlikely it would ever happen we can just follow this path:

js

const result = json.filter(entry => entry.title === responses.stage)

Opening article in a browser ​

As I mentioned - Node ecosystem is crazy wide. There is a library for everything - including cross platform open command: opn.

And just call opn function on the url. That's how we end up with final version of the script:

js

const fetch = require('node-fetch')
const inquirer = require('inquirer')
const opn = require('opn')

class VladCliCommand extends Command {
  static strict = false

  async run() {
    const {flags, argv} = this.parse(VladCliCommand)
    const query = argv.join(' ')

    const searchResponse = await fetch(`https://vladmihalcea.com/wp-json/wp/v2/search?search=${query}`)
    const json = await searchResponse.json()

    let response = await inquirer.prompt([{
      name: 'stage',
      message: '🤔 Choose article',
      type: 'rawlist',
      choices: json.map(function (entry) {
        return {name: entry.title}
      })
    }])

    const result = json.filter(entry => entry.title === response.stage)

    opn(result[0].url)
  }
}

That's more or less it. I skipped some parts like handling connection errors, empty results etc. but I think you got the point.

Publishing to NPM ​

In comparison to Maven official repository, it's very easy to publish a NPM package. You just need to sign up at https://www.npmjs.com/ and run npm publish. If you prefer other distribution than NPM, oclif is your friend. You can build installers for Mac, Windows, Ubuntu/Debian packages and more.

Summary ​

The vlad-cli package is available now to install:

$ npm install -g vlad-cli

It was definitely fun experience, oclif is a fantastic tool and if I only come up with another idea for CLI tool I am definitely going to use it too. The only other tool I can think of I would be keen to use is Quarkus with it's new command mode, but I am not sure how to actually realease quarkus-built-package without putting too much effort into it 😉

Full source code is available at https://github.com/maciejwalkowiak/vlad-cli.

The last but not least - highlight of this whole experiment 🎼 "I can't wait to Hibernate" 🎼 song:

Let's stay in touch and follow me on Twitter: @maciejwalkowiak

Subscribe to RSS feed