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

Mac and Linux user-dir

Linux の sh でユーザーディレクトリのパスを得るのは簡単だ。
最近のバージョンであれば XDG 関連が最初から入っている(と思う)
詳しいことは man xdg-user-dir で。
たとえばドキュメントディレクトリなら

echo xdg-user-dir DOCUMENTS

「そんなコマンドネェよ」という人も大丈夫。
xdg-user-dir の中身は単純なシェルスクリプトなので自作も簡単。

#!/bin/sh

test -f ${XDG_CONFIG_HOME:-~/.config}/user-dirs.dirs && . ${XDG_CONFIG_HOME:-~/.config}/user-dirs.dirs
if [ "x$1" = "xDESKTOP" ]; then
  eval echo \${XDG_${1}_DIR:-$HOME/Desktop}
else
  eval echo \${XDG_${1}_DIR:-$HOME}
fi

何をやっているか理解できれば素敵。
デフォルト引数はこんな場合に使うのか、ふむふむ。

Mac では、困った user-dirs.dirs が無いぞ。
xdg-open には open_darwin() という関数があるのにな。

open_darwin

シェルから得る手段を探しているけど見つからない。
Mac って本当に sh ではたいしたことができないなぁ。
AppleScript か JXA を使いなさいということか。

って、だったら AppleScript を sh から使えばいいんでね?

#!/bin/sh

doc=`osascript -e "POSIX path of (path to documents folder)"`
echo Documents Path: $doc

おぉ!

user_dir

Mac や Linux ではやはり US キーボードでないと駄目だね。
バックコーテーションとチルダはシェルでよく使うから。
コレが日本語キーボードだと嫌がらせとしか思えない位置にある。

おまけ、Visual Studio Code で今頃知った。
command+shift+C でソース位置をカレントに terminal.app が開く。
拡張スクリプトを書こうとしたけど必要無かったYO!

JXA: Finder Get Select Item

JXA から Mac の Finder で現在選択しているファイル名を得たい。
そう、筆者が GNOME の Nautilus でやっていることを Mac で再現したい。

Nautilus をスクリプトで拡張 – L’Isola di Niente

環境変数には何も書き込まれないようだ。
sh ではお手上げだな、AppleScript か JXA しか選べない。

JXA で探しても見つからない、意外とみんな活用していないのかな?
まてよ、AppleScript で探せば歴史があるぶん見つかるんでない?

サンプルのページ

なんだ日本語で見つかるじゃん。
筆者は AppleScript の文法なんてド素人だけど雰囲気でなんとか。

tell application "Finder"
	set selectItems to selection
	set everyItems to every item of selectItems
	delete everyItems
end tell

Finder オブジェクトの selection プロパティが選択 Item の配列。
ということで合っているのかな、JXA でやってみる。
ゴミ箱に捨てても意味ないのでダイアログに ls 表示。

ls_dlg.scpt

let selections = Application("Finder").selection();
let ls = selections.length + " item\n";
selections.forEach(function(item) {
    ls += item.name() + "\n";
});

let app = Application.currentApplication();
app.includeStandardAdditions = true;
app.displayDialog(ls);

今回は scpt でやってみた。
コッチならシバン不要、実行パーミッションを後付けする必要は無い。
更にスクリプトメニューで拡張子が表示されない。

でも肝心な Script Editor.app が残念すぎる。
色分けが即時反映でない、shift+tab も option+↑ 等も使えない。
Gedit や Visual Studio Code に慣れているとイライラするだけ。
生 js で実行パーミッション + テキストエディタが楽でいい。

とにかく Finder で何か選択して実行してみる。

finder_selection

うん、selection オブジェクトは単なる Item 配列として扱えるみたい。
それが解れば後はどうにでもできる、やったね。

つか最近 MacBook Air ばかり使っているような。
マジで大型二輪のサブで 125cc スクーターを買ったみたいな。
一年もすりゃ楽なほうに使用程が逆転するもんだ。

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 への取り込みをマルチスレッドにしたいのに。
そういう使い道には現在は対応していないってことのようです。
いや手段を知らないだけかも、引き続き色々探してみる。