JavaScript」タグアーカイブ

JavaScript Object for-in

つい最近知ったのですが。
JavaScript のオブジェクトで for-in は順序固定になっていた。

#!/usr/bin/gjs

let text = 'あいうえおかきくけこ';
let ls = {};

for (let s of text) ls[s] = s+s+s;

for (let key in ls) print(`Key=${key} Value=${ls[key]}`);

gjs

マジだ、ES2020 かららしい。

これができないのが主な理由で ES6(ES2015) で Map が追加された。
Map なんて誰も使わなかったということなのだろうか。

JavaScript その他 – Paepoi
書き換えしなきゃなぁ、どんどん情報が古くなる。

そういえば Python の連想配列も 3.7 から順序固定になっていたんだっけ。
collections.OrderedDict は存在すら知らない人も多いし。

#!/usr/bin/env python3

text = 'あいうえおかきくけこ'
ls = {}

for s in text: ls[s] = s+s+s

for key in ls: print(f'Key={key} Value={ls[key]}')

まったく同じですね、流行なのかな。
まてよ、もしかして GLib なんかも。

#include <glib.h>
#include <glib/gprintf.h>

void
printfunc(gpointer key, gpointer value, gpointer user_data) {
    g_printf("key=%s value=%s\n", key, value);
}

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

    GHashTable *table;

    table = g_hash_table_new(g_str_hash, g_str_equal);
    g_hash_table_insert(table, "あ", "あああ");
    g_hash_table_insert(table, "い", "いいい");
    g_hash_table_insert(table, "う", "ううう");
    g_hash_table_insert(table, "え", "えええ");
    g_hash_table_insert(table, "お", "おおお");
    g_hash_table_insert(table, "こ", "こここ");
    g_hash_table_insert(table, "け", "けけけ");
    g_hash_table_insert(table, "く", "くくく");
    g_hash_table_insert(table, "き", "ききき");
    g_hash_table_insert(table, "か", "かかか");
    //
    g_hash_table_foreach(table, printfunc, NULL);
    return 0;
}

glib

やっぱり駄目でした、普通はこうなるし。
C を久々に書いてみたけどメンドクセー!

JavaScript filter

最近 JavaScript をまったくやっていないことに気がついた。
ES2023 とかどうなっているんだろう、全然知らないので検索。

JavaScriptのES2023・ES2022の新機能まとめ – ICS MEDIA

jxa

JXA てか JavaScriptCore は ES2023 対応だ、まあ Safari が対応済ですし。
Gjs は駄目だった、おいおい Spider Monkey さん。
ECMA Script を定義しているのって Mozilla さんなのに。

しかし配列の非破壊操作か、Tips を書くときに短くできるメリットはあるけど。
いや大規模な Web アプリとかなら必要になるのかもな。

配列を征する者はJSを制す。JavaScriptのスマートな配列操作テクニック – ICS MEDIA

filter は便利そうですね。
というか Python の内包表記みたいに使えるんでないの。

#!/usr/bin/env python3

ls = ['aa.js', 'bb.py', 'cc.js', 'dd.png', 'ee.js', 'ff.json']

# *.js を抜き出す
jss = [s for s in ls if s.endswith('.js')]
print(jss)

python

#!/usr/bin/gjs

let ls = ['aa.js', 'bb.py', 'cc.js', 'dd.png', 'ee.js', 'ff.json'];

// *.js を抜き出す
let jss = ls.filter(s => s.endsWith('.js'))
console.log(jss)

JavaScript

なんだ便利じゃん、今度からコレ使おう。
あと関係ないかもだけど。

log

Gjs でも console.log が使えるようになっていた、知らなかった!
以前から使えた log とは少し出力が違うのね、いや基本 print を使うけど。

Escape

INI ファイルの読み書きページを更新しました。
INI ファイルの読み書き – Paepoi

セクションを見つける正規表現に最初戸惑った。

EXP = r'^\[\w+\]$'

でイケると思ったけど文字列に半角スペースがあると認識できない。
半角スペースを使うなで済ませようとも思ったけど。

EXP = r'^\[[^\]]+\]$'

そうだ「閉じブラケット以外の文字列なら何でもいい」にすれば!
こんなアホな思いつきに対応できる正規表現ってやはり面白い。

とほほの正規表現入門 – とほほのWWW入門
ところで、とほほさんで見た「ブラケット内は記号の意味を失う」なんですが。

#! /usr/bin/env python3

import re

# [..] にマッチさせる正規表現
EXP = r'^\[[^\]]+\]$'
# EXP = r'^\[[^]]+\]$' # Python OK

a = ['[test]', '[test2]', '[test3] ', ' [test4]', '[test 5]']

for s in a:
    if re.search(EXP, s):
        print(s)

Python

#! /usr/bin/env php

<?php

// [..] にマッチさせる正規表現
$EXP = '/^\[[^\]]+\]$/';
// $EXP = '/^\[[^]]+\]$/'; # PHP OK

$a = ['[test]', '[test2]', '[test3] ', ' [test4]', '[test 5]'];

foreach ($a as $s) {
    if (preg_match($EXP, $s))
        echo $s.PHP_EOL;
}
?>

PHP

#! /usr/bin/gjs

// [..] にマッチさせる正規表現
const EXP = /^\[[^\]]+\]$/;
//const EXP = /^\[[^]]+\]$/; // Gjs NO

let a = ['[test]', '[test2]', '[test3] ', ' [test4]', '[test 5]'];

for (let s of a) {
    if (EXP.test(s)) {
        print(s)
    }
}

Gjs ダメだった。

JavaScript エンジンは複数あるので全部かどうかは試していないけど。
エスケープすれば全部イケたのでブラケット内もエスケープしたほうがいいかと。

Dinamic Import: Gjs and JXA

現在の JavaScript はダイナミックインポートが可能。
下記ページで Chrome や Safari で動くと解りますね。

全モダンブラウザで使えるJavaScriptのdynamic import(動的読み込み) – Qiita

GNOME の Gjs は imports 関数があるので関係ないんだけど。
そういえば macOS の JXA はファイル分割ができないのが困る。
コレを利用してクラス毎に分割できるかな。

#!/usr/bin/osascript

import('./sub.js')
	.then((module) => {
        console.log('Start!');
		const sub = new module.Sub();
        sub.subMethod();
	});

main.js

export class Sub {
    subMethod() {
        console.log('this is Sub Class Method');
    }
}

sub.js

osa

なんでだよ!
Promise だと認識はしているけど動作しないってことみたい。
JavaScriptCore が全部やってくれるってわけじゃないんかい。

Gjs でも同じかな?
console.log を print に書き換えて実験。

#!/usr/bin/gjs

import('./sub.js')
	.then((module) => {
		const sub = new module.Sub();
        sub.subMethod(); 
	});

main.js

export class Sub {
    constructor(app) {
        print('Hello');
    }
    subMethod() {
        print("this is Sub Class Method");
    }
}

gjs

動いたのでコンストラクタも入れてみた、普通に Class ですね。
いや Gjs は imports を使ったほうが簡単ですけど。
というか、Apple は内製スクリプトには本当にヤル気が無いなって。

mpv @ Next File (Nautilus Sort)

しつこいようだけど mpv で次のファイルを再生させるスクリプト。
順番が Nautilus と同じでないのでイマイチ不便だなって。

だったらコレも作ってしまえ!
Gjs なり PyGObject なりでソートさせて出力するコマンドを。
mpv の拡張スクリプトでは無理なのでコマンドを自作する。
それを Lua で呼び出せばなんとかなるはず。

ついでに、いちいち拡張子を指定するのも面倒臭い。
てか Lua がショボいなら自作コマンドのほうで正規表現を使えばいい。
で、GNOME では動画ファイルの ContentType は以下のようになっている。

video/mp4
video/quicktime
video/x-matroska

ContentType なら先頭の video/ だけで動画ファイルを見分けできる。
ということでこんなスクリプトを書いてみた。

#!/usr/bin/gjs

if (ARGV.length == 0) {
    print('usage: nautilus_ls {DIRNAME}');
} else {
    const GLib = imports.gi.GLib;
    const Gio = imports.gi.Gio;
    const re = /^video\//;

    let files = [];
    let d = Gio.file_new_for_path(ARGV[0]);
    let ls = d.enumerate_children('standard::content-type', 0, null);
    for (;;) {
        let info = ls.next_file(null);
        if (info == null)
            break;
        let t = info.get_content_type();
        if (re.test(t))
            files.push(info.get_name());
    }
    files.sort((s1, s2)=> {
        let cmpstr1 = GLib.utf8_collate_key_for_filename(s1, -1);
        let cmpstr2 = GLib.utf8_collate_key_for_filename(s2, -1);
        if (cmpstr1 < cmpstr2)
            return -1;
        return 1;
    });
    print(files.join('\n'));
}

// ex: ft=js

Gjs での例。
nautilus_ls という拡張子無しファイル名でパスの通った場所に保存。
実行パーミッション追加でコマンドの出来上がり。
このコマンドを Lua で呼び出しする。

-- ~/.config/mpv/scripts/next_prev.lua

local utils = require 'mp.utils'

-- Ctrl+DOWN @ Next File Play
function on_nextfile()
    local hit = false
    local directory, fn = utils.split_path(mp.get_property('path'))
    if directory == '.' then
        directory = utils.getcwd()
    end
    local pfile = io.popen('nautilus_ls  "'..directory..'"')
    for filename in pfile:lines() do
        if hit then
            mp.commandv('loadfile', utils.join_path(directory, filename))
            if mp.get_property_bool('pause') then
                mp.set_property_bool('pause', false)
            end
            break
        end
        hit = filename == fn
    end
    pfile:close()
end
mp.add_key_binding('Ctrl+DOWN', 'nextfile_func', on_nextfile)

-- Ctrl+UP Previous File Play
function on_prevfile()
    local prevfn = ''
    local directory, fn = utils.split_path(mp.get_property('path'))
    if directory == '.' then
        directory = utils.getcwd()
    end
    local pfile = io.popen('nautilus_ls  "'..directory..'"')
    for filename in pfile:lines() do
        if filename == fn and prevfn ~= '' then
            mp.commandv('loadfile', utils.join_path(directory, prevfn))
            if mp.get_property_bool('pause') then
                mp.set_property_bool('pause', false)
            end
            break
        end
        prevfn = filename
    end
    pfile:close()
end
mp.add_key_binding('Ctrl+UP', 'prevfile_func', on_prevfile)

イケた!
GNOME 限定です、他の環境の人は参考程度に。