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

JavaScript: padStart

String.prototype.padStart() – JavaScript | MDN

あれ?いつのまにこんなメソッドが追加されたんだろう。
これを使えば JavaScript でもゼロ詰めするだけの処理が不要になるぞと。

'7'.padStart(4, '0');

Fedora 30 で試して SpiderMonkey, JavascriptCore, V8 全部使えると確認。
V8 は node.js でとも思うんだがコレも IE, Android 同様バージョン関係がややこしい。
Apple の SandBox は「全部同梱を強要」という実は Unux と真逆の存在だったりするけど、なるほどって思う。
エロゲ専用に落ちぶれた OS はどうでもいい。

筆者が Fedora, macOS に絞ったのはこの二つのユーザーは確実に最新版を使うという理由もある。
LTS がある Ubuntu がサーバー用途で人気なのも当然、Desktop 版は今やサル専用、完全に宗教。
GNOME2 時代はコレし無いって感じだったのに道を間違えたよね、それはここでは関係なくて。

formatting – Pad a number with leading zeros in JavaScript – Stack Overflow

全部無駄になっちゃった。
でもこういうのを考えるのも経験値だとも。
とりあえず覚書ページに追記しとこう。

PyGObject HANDLES_COMMAND_LINE

前回 *.desktop に追記で ApplicationMenu だった位置に項目追加の件。
あの後アップデート通知から適用して再起動をしたら表示されるようになった。
なんだコレ、*.desktop 項目ってメモリキャッシュでもしているのだろうか?
よくわからないけど前回のリンク先の方法のみで項目追加は可能だと解った。

それより肝心な –new-window オプションの適用手段はなんとかしたい。
GSettings にフラグを用意して転送先 GApplication から参照では強引すぎる。
他に手段があるはず、Nautilus や Gedit はそんな変なことしていないけどメニューにあるし。
ダメだ、筆者の経験値では他の手段を思いつかない。。。。。

素直に Gedit のソースコードを参考にすることにする。
Gedit の About を開いてリンクをクリックすればホームページが開くのでそこから落とす。
当然 C 言語、Python でアプリを作れる人は C でも作れるけど面倒だから(以下略

GtkApplikation の flags に G_APPLICATION_HANDLES_OPEN だけでなく
G_APPLICATION_HANDLES_COMMAND_LINE を OR 演算している、あれ?
この指定って CUI アプリを作る時に使うと思っていた、思い込みだったようだ。

command-line シグナルとのセットのようだ、–new-window をコチラで処理している。
なるほど、–version の場合は転送前の GApplication にて処理。
–new-window は転送先の GApplication で処理ってことね。
handle-local-options と command-line シグナルってそういうことみたい。
こんな便利なものを GApplication は提供していたとは。

ただ同様に G_APPLICATION_HANDLES_COMMAND_LINE を OR で色々試してみたんだけど。。。。。
どうやっても open シグナルが発行されない、これじゃファイル名を得られない。
devhelp には G_APPLICATION_HANDLES_OPEN 指定なら残りの引数で open シグナルとあるんだが。
Google 翻訳なので完全に正しいとはいえないんだけど、とにかく困った。

よく見ると Gedit もファイル引数は独自で処理しているみたいだし。
あきらめて command-line ハンドラ内で処理するしかないみたい。
get_arguments で引数は全部取得できる、存在するファイルかどうかを自前で調べ
g_application_open を発行という手もあるけど二度手間だよね。

経緯を全部書いたら長くなってしまった。
ということをふまえて早速サンプルコードを書いてみる。

#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gio

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Chikubi')
        self.resize(150, 60)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
            self,
            application_id='bura.oppai.chikubi',
            flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
            # nonsense
            #flags=Gio.ApplicationFlags.HANDLES_OPEN | Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
        # add --version, -v option
        self.add_main_option('version', b'v', GLib.OptionFlags.NONE, GLib.OptionArg.NONE, 'Show Chikubi Version', None)
        # add --new-window, -n option
        self.add_main_option('new-window', b'n', GLib.OptionFlags.NONE, GLib.OptionArg.NONE, 'New Chikubi Window', None)

    def do_command_line(self, command_line):
        # --new-window Option Handler
        options = command_line.get_options_dict()
        if options.lookup_value('new-window', GLib.VariantType.new('b')):
            w = Win(self)
        # get filename
        arg = command_line.get_arguments()[1:]
        for s in arg:
            # Option ignored
            if not s.startswith('-'):
                # URI? (file:///...)
                if '//' in s:
                    f = Gio.file_new_for_uri(s)
                else:
                    f = Gio.file_new_for_path(s)
                if f.query_exists():
                    self.props.active_window.set_title(s)
                    break
        return 0

    def do_handle_local_options(self, options):
        # --version Option Handler
        if options.lookup_value('version', GLib.VariantType.new('b')):
            print('Chikubi 0.0.1')
            return 0
        return -1

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

''' needless
    def do_activate(self):
        self.props.active_window.present()

    def do_open(self, files, n_file, hint):
        self.props.active_window.present()
'''

if __name__ == '__main__':
    app = App()
    app.run(sys.argv)

狙ったとおりになった。

*.desktop で %U 指定だと file:///… の URI で渡ってくるので注意ね。
パス名は二連スラッシュに絶対できないはずだから振り分けはコレでイケると思う。
もう少し自前 debug してから自アプリは更新します。

PyGObject handle_local_options

GNOME 3.32 から ApplicationMenu は無くなったはずだけど。
Gedit や Nautilus の Global Menu には「新しいウインドウ」がある。
自作 GTK+ アプリにもコレを付けてみたい。

“New terminal” desktop action does nothing on Gnome ? Issue #427 ? gnunn1/tilix ? GitHub

Desktop Entry Specification

色々探してこんなのを見つける。
*.desktop のほうに定義するのね、なるほど。

いやまて。
つまり –new-window というオプションを用意しなきゃいけないヤン!

手抜きなオプション定義にしていたけどキチンと作らないと。
GApplication の handle_local_options シグナルを使えばいいようだ。

Using g_application_add_main_option_entries() with PyGObject ? GitHub

GOptionEntry を使おうとしたら PyGObject だとメンドクサ!
g_application_add_main_option のほうが短いコードでイケるのでコッチで。
とりあえず –version を表示するオプションで上手くいったサンプルコード。

#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Oppai')
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)
        # add --version, -v option
        self.add_main_option('version', b'v', GLib.OptionFlags.NONE, GLib.OptionArg.NONE, 'Show Oppai Version', None)

    def do_handle_local_options(self, options):
        # get option
        if options.lookup_value('version', GLib.VariantType.new('b')):
            self.show_version()
            return 0
        return -1

    def do_activate(self):
        self.window = Win(self)
        self.window.present()

    def show_version(self):
        print('Oppai 0.0.1')

if __name__ == '__main__':
    app = App()
    app.run(sys.argv)

にて。

–help は全自動で定義される。
add_main_option の第二引数を使う場合はバイナリにするのをお忘れなく。
handle_local_options ハンドラはゼロを戻すとそのまま終了してくれる。
それ以外の説明はいらないよね。

さて肝心の –new-window を定義したら色々困ったぞ。
handle_local_options は startup シグナルより前に飛んでくる。
なので何も定義されていない状態。
しかも GApplication は同じ application-id の app に引数を転送して終了する。
handle_local_options ハンドラからは転送先 app を参照することは不可能。
つまり GtkApplicationWindow はこのハンドラ内では作れないってことだ。

GSettings にフラグを用意するというかなり強引な方法で定義はできた。
コマンドでは上手くいった、よし comipoli.desktop に追記だ。
表示されないんですけど。。。。。
まだ他にやらないといけないことがあるのだろうか?

GLib(PyGObject) Tips

内容が古いと解っていながら放置していた PyGObject Tips 以下。
整理したり文字列の fstring 化等をやってとりあえず GLib のページだけ。
GLib(PyGObject) Tips – L’Isola di Niente

それでは寂しいので GIOChannel 関連を Gjs のページからもってくる。
GIOChannel(PyGObject) Tips – L’Isola di Niente

いざ書き換えしてみたらマジで内容が古かった、こりゃアカン。
盆休みくらいまでには全部書き換えしたいな、ポケ GO も飽きたし。

PyGObject で作りたいものは今は無いけどページを作っていたら何か思い付くかも。
PyObjC では実はあるんだけど手段で詰まってアプリもページも止まっていることは内緒。
zsh 関連は次の macOS アップデートから再開、今やってもなぁって感じだし。

しかし Python を始める人が増えた感じだよね。
C 言語のポインタを理解できない人がオブジェクトなんて理解できるのか疑問なんだけど。
Python でアプリが作れる人って実は C でも作れるけど面倒だから Python を使っている。
っていう人しかいないんだよね。

Open Terminal in Atom and Gedit

macOS 上の Atom で Terminal.app を一発で開きたい。
もちろん編集中ファイルの位置をカレントディレクトリにして。
Gedit, VScode には最初からその機能があったけど Atom には無いのね。

早速拡張をインス、、、、、なんてしないよ。
拡張のインストールネタをやっている輩は何もアプリが作れない人だけです(暴言)
プログラミングの実力は経験値だけなのに他人が作ったものに頼っている時点でねぇ。

それと Atom ウインドウ内部で開くターミナルを勧めている輩は何だよ。
debug 目的なんだから別プロセスにしなきゃ困るだろ、と思うんだが。
って、それは別の話ということで。

Atom は Node.js 製なので child_process.exec が使える。
Gjs, jjs, JXA, Node.js – L’Isola di Niente
後はカレントディレクトリを得る手段さえ解ればアッサリ作れるはず。

いや、coffeescript という Atom の拡張以外の使い道が思いつかない言語が。。。。。
世の中にはレールなんとか以外の使い道がゼロの言語を推すみたいな人もいるけどさ。
面倒だ、既存拡張のソースを見てしまえ!

GitHub – blueimp/atom-open-terminal-here: Open the Terminal (Mac OS X, Linux) or Command Prompt (Windows) in the given directory via context menu or keyboard shortcut in the Atom text editor.

index.coffee を見る。
child_process.exec は coffeescript ではこう書けばいいのか。
atom.workspace.getActivePaneItem() から辿って編集中パスが得られるようだ。
x-terminal-emulator って何かと調べたらサル専用ディストリだけじゃん。
サルブンツ以外の Linux 使いは結局自分で作らないといけなかったわwwwww

そういえばテンプレートリテラルは coffeescript ではどう描くの?
Template literals in Coffeescript (Example)
ダブルクォートとシャープって何よ、JS と同じバッククォートとドルでいいじゃん。
従うしかないけーが、ダブルクォートのエスケープめんどいな。

Atom を Gedit のように使う – L’Isola di Niente
これに追記する。

init.coffee

atom.commands.add 'atom-text-editor', 'editor:open-terminal', ->
    path = atom.workspace.getActivePaneItem()?.buffer?.file?.path
    dirname = require('path').dirname(path)
    require('child_process').exec "open -a Terminal \"#{dirname}\""

keymap.cson

'atom-workspace atom-text-editor:not([mini])':
    #etc...
    'alt-cmd-t': 'editor:open-terminal'

command+option+T で Terminal.app を開く機能追加おしまい。
ほとんどコピペだけどこの手段が解ってしまえば応用もできるし書き方も覚えた。
経験値はこんなことの積み重ねです。

ちなみに筆者は自前拡張呼び出しは command+option+* に統一している。
Gedit では Ctrl+Alt+* と同じように統一している。
Gedit 及び Eye of Gnome プラグイン – L’Isola di Niente
そうでもしないと自分で指定したはずのキーを結構忘れるんですよコレが。
拡張を沢山インストールって人は他人が決めたキーを全部覚えるのだろうか?

おまけ。

Gedit にこの機能は最初からあるけど 3.32 は起動すると stderr を吐く。
端末から gnome-terminal と打って起動すれば同じ stderr を吐くのが解る。
ということで標準の外部ツールを書き換えしましょう。

#!/bin/sh

gnome-terminal --working-directory=$GEDIT_CURRENT_DOCUMENT_DIR > /dev/null 2>&1 &

Ctrl+Alt+T にして入出力はすべて無しに。
これで stderr の赤い出力が出なくなるのでボトムパネルも開かなくなります。