Programming」カテゴリーアーカイブ

Open new Terminal.app with Finder Current Directory

Mac の Finder から Terminal.app を開きたい。
最初から今開いているディレクトリをカレントディレクトリにして。
もちろん JXA を使って自作拡張スクリプトとして。

日本語で探すとみんなアプリを紹介している。
こういう人達って本当にプログラミングをやっているの???
その程度なら自分で作ろうと考えるのが当然だと思うんですけど。
まあそれはいいとして。

最初は sh でやろうと思ったけど pwd が $HOME になってしまう。
どうやら JXA で Finder から取得する必要があるようだ。

Open a new Terminal window for the current Finder folder – macro – Keyboard Maestro Discourse

やっぱり海外でしか手段が見つからないなぁ。

しかし上記をそのまま拡張ディレクトリに突っ込んでも動かない。
Finder から呼び出すので Finder の有無を調べる必要が無い。
つか何をやっているか解りづらいよ、読みやすく書き替えた。

#!/usr/bin/osascript

let terminal = Application("Terminal");
let finder = Application("Finder").finderWindows();

let uri = finder[0].target().url();
let path = decodeURI(uri).slice(7);

terminal.doScript("cd " + path);
terminal.doScript("clear");
terminal.activate();

このくらい分割すれば何をどうやっているか解ると思う。
しかしこの手段では 1 ページ分スクロールになってしまいイマイチ。
筆者はコッチを勧める。

terminal.js

#!/usr/bin/osascript

let finder = Application("Finder").finderWindows();

let uri = finder[0].target().url();
let path = decodeURI(uri).slice(7);

let app = Application.currentApplication();
app.includeStandardAdditions = true;
app.doShellScript("open -a Terminal " + path);

と。
open コマンドの最後にフルパスを付ければカレントディレクトリになる。
コイツを実行パーミッションを付加して拡張ディレクトリに移動

chmod +x terminal.js
mv terminal.js ~/Library/Scripts/Applications/Finder

これでスクリプトが利用できる。
Finder がアクティブの状態でメニューバーのスクリプトアイコンを。

do_script

うん、コレで JXA を実行するのがちょっとだけ楽になったぞい。
とりあえずこれだけ理解できればいくらでも拡張できると思う。

macOS Sierra JavaScript

しまった、忘れていた!
JXA は var キーワードとセミコロンは不要だった。

前回 macOS Sierra では JavaScript にて let が使えると書いた。
ならば let キーワードなら let で、無宣言なら var と同じで合っている?

というか本当に let の仕様どうりなのか?
ちゃっちゃと書いて試してみる。

#!/usr/bin/osascript

// @ macOS Sierra

// let Test
let test1 = "let Keyword";
for (let i=0; i<2; i++) {
    let test1 = "for " + i;
    //console.log(test1);
}
console.log(test1); //=> let Keyword

var test2 = "var Keyword";
for (let i=0; i<2; i++) {
    var test2 = "var " + i;
    //console.log(test2);
}
console.log(test2); //=> var 1

test3 = "No Keyword == var";
for (let i=0; i<2; i++) {
    test3 = "Null " + i;
    //console.log(test3);
}
console.log(test3); //=> Null 1

やはり。
let キーワードだとブロックの内側に左右されないと確認できた。

ついでに、最近知ったけど Swift の let キーワードって定数宣言なのね。
まぎらわしいよ、何故 const にしなかったのだろう?

そうそう、let は JavaScript 1.7 で追加されたキーワード。
JavaScript 1.7 にフル対応しているのかな?
つまり yield ジェネレーター等も使えるのだろうか。

JavaScript 1.7 の新機能 – JavaScript | MDN

#!/usr/bin/osascript

// Generator Test
function get_yield() {
    let ss = ["first", "second"];
    yield ss[0];
    yield ss[1];
}
let g = get_yield()
console.log(g.next());
console.log(g.next());

yield_test

駄目だ、yield と書いた時点でエラーになる。
イテレータもエラー、Iterator 関数では何故かエラーにはならない。

どうやら 1.7 の一部に対応ということみたい。
まあ今後のバージョンアップで上記も対応してくれるだろう。

それと、少し話がズレるけど。
関数でタプル(配列)を戻す場合に Gjs と同じように取得できるように。

#!/usr/bin/osascript

// Tuple Test
function get_tuple() {
    return [1, 2, 3, "Daaaaaaaaaa!"];
}
let [aaa, bbb, ccc, ddd] = get_tuple();
console.log(aaa);
console.log(bbb);
console.log(ccc);
console.log(ddd);

地味に解りやすい。

ちなみに Gjs は全部問題なく 1.7 仕様が使える。
まあそりゃ実行エンジンが spidermonkey ですし。

gjs_yield

V8 エンジンの Node.js はみんな var で書いているけどどうなんだろう?
機会が無くていまだに手を出していないんだけど。

Python multiprocessing

17.2. multiprocessing ? プロセスベースの並列処理 ? Python 3.5.2 ドキュメント

Python にはこんな標準モジュールがあったんだ。
コレでマルチコア CPU をフル活用できるかも、早速使ってみよう。

#!/usr/bin/env python3

import multiprocessing

arr = [1, 2, 3]

def on_mp(num):
    print(num * 2)
    arr.append(num * 2)

for num in arr:
    p = multiprocessing.Process(target=on_mp, args=(num,))
    p.start()
    p.join() 

print(arr)

''' output
2
4
6
[1, 2, 3]
'''

…なんだこれ。

[1, 2, 3, 2, 4, 6]
が期待できるけどならない、例外もエラーも吐かない。
append した整数はいったいどこに格納されたんでしょう?

日本語で探しても皆 print ばかり、気がついていない人って多いかも。
せっかくマルチスレッドにしても変数に値を格納できなきゃ無意味だ。
色々探してなんとか見つけた。

multithreading – Python multiprocessing.Pool: when to use apply, apply_async or map? – Stack Overflow

apply_async メソッドの callback 指定からなら変数に入れられるようだ。
クラスメソッドでも大丈夫なのかな?試してみる

#!/usr/bin/env python3

import multiprocessing

class MpTest:
    def __init__(self):
        self.arr = [1, 2, 3]
        pool = multiprocessing.Pool()
        for num in self.arr:
            pool.apply_async(self.on_pool, args=(num,), callback=self.on_pool_result)
        pool.close()
        pool.join()

    def on_pool(self, num):
        return num * 2

    def on_pool_result(self, result):
        self.arr.append(result)

app = MpTest()
print(app.arr)
''' output
[1, 2, 3, 2, 4, 6]
'''

うん大丈夫。
よしコレで PyGObject でも本格的なマルチスレッドが。

#!/usr/bin/env python3

import multiprocessing, gi, sys
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, Gdk, GdkPixbuf

GF_PATH = "/home/sasakima-nao/pic/test"

class MpTest(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        button = Gtk.Button(label="show CBZ")
        button.connect("clicked", self.on_button_clicked)
        self.fbox = Gtk.FlowBox(valign=Gtk.Align.START, max_children_per_line=10, min_children_per_line=10)
        sc = Gtk.ScrolledWindow()
        sc.add(self.fbox)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.pack_start(button, False, False, 0)
        vbox.pack_start(sc, True, True, 0)
        self.add(vbox)
        self.resize(800, 600)
        self.show_all()

    def on_button_clicked(self, button):
        d = Gio.file_new_for_path(GF_PATH);
        infolist = d.enumerate_children(
            Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
            Gio.FileQueryInfoFlags.NONE)
        pool = multiprocessing.Pool()
        for info in infolist:
            name = info.get_display_name()
            pool.apply_async(self.on_pool, args=(name,), callback=self.on_pool_result)
        pool.close()
        pool.join()

    def on_pool(self, name):
        s = "{0}/{1}".format(GF_PATH, name)
        p = GdkPixbuf.Pixbuf.new_from_file(s)
        minp = p.scale_simple(80, 100, GdkPixbuf.InterpType.BILINEAR)
        image = Gtk.Image(pixbuf=minp, visible=True)
        return image

    def on_pool_result(self, result):
        """
            Error @ result is a Python Type Onry ?
        """
        self.fbox.add(result)

class MpApp(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        MpTest(self)

app = MpApp()
app.run(sys.argv)

できなかった。

error_no_gtkwidget

callback で得られる引数を GtkWidget と認識できないようです。
裏技っぽく list にして戻しても駄目、GdkPixbuf を戻すようにしても駄目。
文字列や整数なら問題無し、Python の型以外は戻せないってことみたい。

スレッド自体は動いているから CPU 負荷を見てみる。

cpu_weit

うん見事にマルチコアをフル活用できている。
でも前回 C で作ったものより負荷が多い、速さでは話にならないな。
とはいえシングルスレッドよりはるかに速いのは間違いない。

一番負荷が掛かる GdkPixbuf への取り込みをマルチスレッドにしたいのに。
そういう使い道には現在は対応していないってことのようです。
いや手段を知らないだけかも、引き続き色々探してみる。

GTask

Python の threading はあまり意味が無いので有名。
じゃあマルチスレッドは GLib でやればいいんでないの?
GLib (Gio) でのマルチスレッドは GTask で簡単にできるらしい。

早速 PyGObject で…
g_task_run_in_thread 関数がバインドされていないヤン!

no_run_in_thread

Gjs からも当然使えないってことだよね。
ならば Vala で、って valadoc のどこにも書いていない…

しかたがない、久々に C 言語でやってみるか。
画像が沢山あるディレクトリから GdkPixbuf をリサイズして取り込む例。
マルチスレッドで取り込み次第順次表示していく感じで。

GF_PATH 定数は自前で画像の多いディレクトリに書き換えてね。

#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

/*
gcc tasktest.c `pkg-config --cflags --libs gtk+-3.0`
*/

#define GF_PATH "/home/sasakima-nao/pic/test"

GtkWidget *flowbox;

void
task_cb (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) {

    GdkPixbuf *pixbuf;
    GtkWidget *image;

    pixbuf = gdk_pixbuf_new_from_file_at_size (task_data, 80, 100, NULL);
    image = gtk_image_new_from_pixbuf (pixbuf);
    gtk_widget_show (image);
    gtk_container_add (GTK_CONTAINER (flowbox), image);
    g_free (task_data);
    g_object_unref (task);
}

void
button_click_cb () {

    GFile *file;
    GFileEnumerator *dirlist;
    gssize result;

    file = g_file_new_for_path (GF_PATH);
    dirlist = g_file_enumerate_children(file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL);
    while (TRUE) {
        GFileInfo *info;
        if (!g_file_enumerator_iterate (dirlist, &info, NULL, NULL, NULL))
            break;
        if (!info)
            break;
        GTask *task;
        gchar *fullpath;
        fullpath = g_strdup_printf ("%s/%s", GF_PATH, g_file_info_get_display_name (info));
        task = g_task_new (NULL, NULL, NULL, NULL);
        g_task_set_task_data (task, fullpath, NULL);
        g_task_run_in_thread (task, task_cb);
    }
    g_object_unref (dirlist);
}

int
main (int argc, char *argv[]) {

    GtkWidget *window;
    GtkWidget *button;
    GtkWidget *vbox;
    GtkWidget *scrolled;

    gtk_init (&argc, &argv);
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    button = gtk_button_new_with_label ("clicke!");
    g_signal_connect (G_OBJECT (button), "clicked", button_click_cb, NULL);
    flowbox = gtk_flow_box_new ();
    gtk_widget_set_valign (GTK_WIDGET (flowbox), GTK_ALIGN_START);
    vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), flowbox, TRUE, TRUE, 0);
    scrolled = gtk_scrolled_window_new (NULL, NULL);
    gtk_container_add (GTK_CONTAINER (scrolled), vbox);
    gtk_container_add (GTK_CONTAINER (window), scrolled);
    g_signal_connect (G_OBJECT (window), "delete_event", gtk_main_quit, NULL);
    gtk_window_resize (GTK_WINDOW (window), 600, 400);
    gtk_widget_show_all (window);
    gtk_main ();

    return 0;
}

面倒臭え!
何故 C で作りたい初心者が多いのか理解できネェ!!
GTask の破棄はコレでいいのかよくワカンネェ(ぅぉい!

とにかく起動してボタンを押してみる。

murti_core

うん、これだけで見事に CPU コアをフルに使って動いている。
画像も一枚ずつ順次表示される、なるほど。

これで本格的マルチスレッドもバッチリ。
って、Comipoli で使いたいんで Python でないと困るんだけーが。

GNOME Gsf

こんなの見つけた。

Zip files: GSF Reference Manual

何だよ、GNOME には ZIP ファイルを扱うライブラリがあったのか。
GLib や Gio ばかり見ていて気が付かなかった。

gsf_gir

Fedora 24 には普通に最初から libgsf が入っていますね。
GObject Introspection からも使える、これは使わない手はない。
以下は Vala だけどサンプルコードも見つかった。

Projects/Vala/GSFSample – GNOME Wiki!

早速コードを書いてみたけど色々困った。
gsf_infile_num_children はアーカイブの一階層分しか得ることができない。
階層がある場合は gsf_infile_num_children の戻り値を再帰するしかない。

しかも二階層以下はソートが名前の逆順に。
Python の ZIP ファイルもそうだったけど何故だ?
しかたがないので一階層以降は逆順の再帰で。

ついでに、サイズ取得が何故かプロパティ。
gst_input_size は size_t で size property は int64 だし。
意味ワカンネェけど動いたからいいや。

#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gsf', '1')
from gi.repository import Gtk, Gio, GdkPixbuf, Gsf

PATH = "ziptest.cbz";
#PATH = "なえコレ.zip"

class GsfTest(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.datas = []
        # unzip
        f = Gsf.InputStdio.new(PATH)
        infile = Gsf.InfileZip.new(f)
        self.read_zipfile(infile)
        # add
        fbox = Gtk.FlowBox(valign=Gtk.Align.START, min_children_per_line=5)
        for data in self.datas:
            fbox.add(data)
        self.add(fbox)
        self.show_all()

    def read_zipfile(self, infile):
        num = infile.num_children()
        for i in range(num):
            item = infile.child_by_index(i)
            if item.size == 0:
                self.read_childfile(item)
            else:
                self.append_data(infile, item)

    def read_childfile(self, infile):
        num = infile.num_children()
        for i in range(num-1, -1, -1):
            item = infile.child_by_index(i)
            if item.size == 0:
                # recursive function
                self.read_childfile(item)
            else:
                self.append_data(infile, item)

    def append_data(self, infile, item):
        data = item.read(item.size)
        try:
            stream = Gio.MemoryInputStream.new_from_data(data)
            p = GdkPixbuf.Pixbuf.new_from_stream(stream)
            minp = p.scale_simple(80, 100, GdkPixbuf.InterpType.BILINEAR)
            image = Gtk.Image(pixbuf=minp)
            self.datas.append(image)
        except Exception as e:
            print(e)


class GsfApp(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        GsfTest(self)

app = GsfApp()
app.run(sys.argv)

コレでイケたけど。
階層の名前を取り出してソートしたほうが無難だろうか?

それより問題は速度がどうなるかだ。
Comipoli beta3 | PaePoi
の時からどう変わったか Comipoli に組み込んで同じ巨大ファイルを。

gsf_cpu

あんまり変わんねえヤン…
期待したのに。
せっかく安定して動いているのを変えるべきか悩む。