vimでvim-fireplaceをインストールしてreplで式を評価するまでの手順

新しく配属された人がvim使いだったので
replを使って式を評価するってところまでを調べてやってみた。

まぁそうそう使うことは無いだろうけど、必要になった時のためにということで。

plug.vimインストール

curl -fLo ~/.vim/autoload/plug.vim --create-dirs     https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

.vimrcを編集

if has('vim_starting')
  set rtp+=~/.vim/plugged/vim-plug
  if !isdirectory(expand('~/.vim/plugged/vim-plug'))
    echo 'install vim-plug...'
    call system('mkdir -p ~/.vim/plugged/vim-plug')
    call system('git clone https://github.com/junegunn/vim-plug.git ~/.vim/plugged/vim-plug/autoload')
  end
endif


call plug#begin('~/.vim/plugged')
Plug 'tpope/vim-fireplace', { 'for': ['clojure'] }
call plug#end()

vimを開いてvim-fireplaceをインストール

:PlugInstall

いったんVimを閉じる

サンプル用のプロジェクトをひとつ作ってREPLを立ち上げる

mkdir ~/vim-test
cd ~/vim-test
lein new test
cd test
lein repl

vimでファイルを開く

vim src/test/core.clj
※ barという関数をここに新しく作る(strとかを返すものだとわかりやすい)

式を評価

; もしかしたらこれも必要かも
; :%Eval
:Eval (test.core/bar)

参考にしたサイト

d.hatena.ne.jp github.com hifistar.hatenablog.com haiju.hatenablog.com

flash-get!を使っているページがreplで動かない

ログイン用の簡単なHTMLを出すページをreplとかで動かそうとしたんだけど

ClassCastException clojure.lang.Var$Unbound cannot be cast to java.util.concurrent.Future  clojure.core/deref-future (core.clj:2184)

こんなエラーが出て動かなかった。

print-stack-traceでコードを追っかけてみると
どうやらnoir-sessionのflash-getしてるとこで落ちてる模様

user=> (print-stack-trace *e 30)
java.lang.ClassCastException: clojure.lang.Var$Unbound cannot be cast to java.util.concurrent.Future
 at clojure.core$deref_future.invoke (core.clj:2184)
    clojure.core$deref.invoke (core.clj:2207)
    noir.session$flash_get.invoke (session.clj:151)
    noir.session$flash_get.invoke (session.clj:149)
    sample01.page$base_layout.doInvoke (page.clj:115)
    clojure.lang.RestFn.invoke (RestFn.java:442)
    sample01.page$login_page.invoke (page.clj:146)
...

ググって調べてみるとStackOverflowに
「REPLで動かすときはこうすりゃ動くぜ!でもプロダクションには使わないでね(自分解釈)」
と書いてあったので

(binding [sesh/*noir-session* (atom {:somekey "somevalue"})]
  (sesh/put! :user "borkdude"))

試してみるもエラーは変わらず。

(binding [sesh/*noir-session* (atom {})] (p/login-page {} {}))

stackoverflow.com

仕方ないので、flash-getのコードを眺めてみると

...
(declare ^:dynamic *noir-flash*)

(defn flash-put!
  "Store a value that will persist for this request and the next."
  [k v]
  (clojure.core/swap! *noir-flash* assoc-in [:outgoing k] v))

(defn flash-get
  "Retrieve the flash stored value."
  ([k]
     (flash-get k nil))
  ([k not-found]
   (let [in (clojure.core/get-in @*noir-flash* [:incoming k])
         out (clojure.core/get-in @*noir-flash* [:outgoing k])]
     (or out in not-found))))

(defn ^:private noir-flash [handler]
  (fn [request]
    (binding [*noir-flash* (atom {:incoming (:flash request)})]
      (let [resp (handler request)
            outgoing-flash (:outgoing @*noir-flash*)]
        (if (and resp outgoing-flash)
          (assoc resp :flash outgoing-flash)
          resp)))))
...

あれ、もしかして*noir-session*じゃなくて*noir-flash*なんじゃね?
と感じたのでやってみるとうまくいった。

(binding [sesh/*noir-flash* (atom {})] (p/login-page {} {}))

今日はエスパー力高かった。

Clojureの内包表記

こんなテーブルから

+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| id           | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| display_name | text         | NO   |     | NULL    |                |
| email        | varchar(255) | NO   | UNI | NULL    |                |
| user_name    | varchar(32)  | NO   | UNI | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

idとdisplay_nameだけを取り出した

[[1 "user_name_1"] [2 "user_name_2"] [3 "user_name_3"]]

こんな形のデータを作りたくて(hiccupのdrow-downに渡すデータ)調べたんだけど
なかなかいいサンプルが見つからなかったので結局REPLでガチャガチャやって解決。

; usersにselect * from userの結果が入っている
(for [user users] [(:display_name user) (:id user)])

こんな感じでいけた

for - clojure.core | ClojureDocs - Community-Powered Clojure Documentation and Examples
ここのサンプルを見ると、:whenを使うことでフィルタリングする条件とかもつけられるらしい。
これは便利そう。

ちなみに、持ってきたデータの先頭に「選択してください」みたいな
valueが空のデータを追加したい場合はconsを使うと出来た

(cons ["選択してください" ""] (for [user users] [(:display_name user) (:id user)]))

Clojureは表現力が高く、とても良い言語なんだけどまだ慣れないなー。

Windowsのコマンドラインからzipファイルを作る

20個くらいのファイルを加工してからzip圧縮する といった処理で使った。

#cygwin上から実行
$ 7z a test_file.zip test_file.csv

参考: d.hatena.ne.jp

MySQLでCSVの指定のフィールドをロードする

フィールド名を書くところに@dummyのような
捨てる変数を割り当てるとうまくいった。
こんなことも出来たんだな。
これだとわざわざcsvを編集しなくてもいいから便利。

mysql> LOAD DATA LOCAL INFILE '/tmp/test_table.csv' 
INTO TABLE test_table 
FIELDS TERMINATED BY ',' 
IGNORE 1 LINES           
(
  @dummy, 
  field1, 
  @dummy, 
  field2, 
  @dummy, 
);

ちなみに、最近のバージョンではLOAD DATA LOCAL INFILEはログイン時に
パラメータを渡さないと使えないらしい。

$ mysql -u user -puser db1 --local-infile=1

非推奨なのかな?あとでちゃんと見るか。

参考:

CSVの特定のフィールドをインポート 【OKWave】

SQL脳向けのmongodbの集計クエリ

mongodbのaggregateフレームワークに全然なれることが出来ない。
10年以上かけて出来たSQL脳にはハードルが高いよねコレ。

SELECT
  group_key1,
  group_key2,
  group_key3,
  sum(num_field1) as num_field1_sum,
  min(num_field2) as num_field2_sum,
  max(num_field3) as num_field3_sum
FROM
  data_table
GROUP BY
  group_key1,
  group_key1,
  group_key1

こんなSQLと等価なクエリを書くには

db.data_table.aggregate([
  { $match: {"xxx": "xxx"}} , // 必要なら絞り込みの条件を追加 
  { $group: { _id: {
    "group_key1": "$group_key1", 
    "group_key2": "$group_key2", 
    "group_key3": "$group_key3" }, // _idのとじカッコに注意。
    "num_field1_sum":{"$sum": "$num_field1"}, 
    "num_field2_sum":{"$min": "$num_field2"}, 
    "num_field3_sum":{"$max": "$num_field3"} 
    } 
  } 
]);

こうするみたい。