Paepoi

Paepoi » JavaScript Tips » Gjs, JXA, Node.js

Gjs, JXA, Node.js

# 最終更新日 2024.03.30

- Node.js 関連を ESM 化、及び promises 仕様に書き換えしました
- 動作確認バージョンを追記しました

スタンドアロン(単独で利用できる)の JavaScript 実行環境での基本機能の覚書
UNIX 系(macOS 含む)のみで動作確認

通称コマンド名動作環境コア動作確認
GjsgjsGNOMESpiderMonkey1.78
JXAosascriptmacOSJavaScriptCore10.11(放置)
Node.jsnodeマルチV820.01

標準入出力
echo ほげ | gjs test.js のようなパイプ入力にも対応
/**
 * Gjs (gjs コマンドに -m オプションが必要)
 */

import GLib from 'gi://GLib';

// readline は自作
let readline = function(prpt) {
    // stdin=0 stdout=1
    let channel = GLib.IOChannel.unix_new(0);
    let stdin = '端末入力';

    if (channel.get_flags() == GLib.IOFlags.IS_READABLE) {
        stdin = 'パイプ';
    } else {
        channel.write_chars(prpt, -1);
    }
    let [status, str_return] = channel.read_line();
    let line = str_return.trimEnd();
    return [stdin, line];
}

let [stdin, line] = readline('何か入力して Enter :');
console.log(`${stdin}から ${line} が渡されました`);

/**
 * JXA (GNU readline 同様に使える)
 */

ObjC.import('readline');
ObjC.import('unistd');

let prefix = $.isatty(0) ? '端末入力' : 'パイプ';
let line = $.readline('何か入力して return :');
console.log(`${prefix}から ${line} が渡されました`);

/**
 * Node.js (拡張子を *.mjs にする必要があります)
 */

import * as readline from 'node:readline/promises';
import * as tty from 'node:tty';
import { stdin as input, stdout as output } from 'node:process';

let rl = readline.createInterface({ input, output, terminal:false });

let [prpt, prefix] = tty.isatty(0) ? ['何か入力して return :', '端末入力'] : ['', 'パイプ'];

let line = await rl.question(prpt);
console.log(`${prefix}から ${line} が渡されました`);
rl.close();

ファイルの読み書き
Gjs は Gio を使ったほうがいいけどそれは別のページにて
/**
 * Gjs
 */

import GLib from 'gi://GLib';
  
const FILE = 'write_gjs.txt';
const TEXT = `出力では JavaScript 文字列のままイケます
入力は Uint8Array になりますので以下のように`;
 
let result = GLib.file_set_contents(FILE, TEXT); // Write
if (result) {
    console.log('Write Success');
}
let [result2, contents] = GLib.file_get_contents(FILE); // Read
if (result2) {
    let dec = new TextDecoder();
    let text = dec.decode(contents);
    console.log(text);
}

/**
 * JXA
 */

ObjC.import('Cocoa');

const FILE = $('write_jxa.txt');
const TEXT = $('日本語を書き込むよ\n改行もしてみるよ');
let nil = $();

// Write
let res = TEXT.writeToFileAtomicallyEncodingError(FILE, true, $.NSUTF8StringEncoding, nil);
if (res) {
    console.log('Write Success!');
}

// Read
let s = $.NSString.alloc.initWithContentsOfFileEncodingError(FILE, $.NSUTF8StringEncoding, nil);
console.log(s.js);

/**
 * Node.js
 */

import fs from 'node:fs/promises';
 
const FILE = 'write_node.txt';
const TEXT = '日本語を書き込むよ\n改行もしてみるよ';

try {
    await fs.writeFile(FILE, TEXT);
    console.log('Write Success!');
} catch(err) {
    console.log(err);
}

try {
    let content = await fs.readFile(FILE, 'utf8')
    console.log(content.trim());
} catch(err) {
    console.log(err);
}

起動パラメータ
Node.js はシェルそのままなパラメータになる、他は全部引数のみの配列
/**
 * Gjs (1.52 からUNICODE 化されました)
 */

if (ARGV.length == 0) {
    console.log('No argv');
} else {
    for (let s of ARGV) {
        console.log(s)
    }
}

/**
 * JXA
 */

function run(argv) {
    if (argv.length == 0) {
        console.log('No argv');
    } else {
        for (let s of argv) {
            console.log(s);
        }
    }
}

/**
 * Node.js (実行ファイル名, スクリプトファイル名, argv[0], argv[1], ...)
 */

if (process.argv.length < 3) {
    console.log('No argv');
} else {
    for (let s of process.argv) {
        console.log(s);
    }
}

環境変数
$HOME を表示する例
/**
 * Gjs
 */

import GLib from 'gi://GLib';
console.log(GLib.getenv('HOME'));

/**
 * JXA
 */

ObjC.import('stdlib');
console.log($.getenv('HOME'));

/**
 * Node.js
 */

console.log(process.env.HOME);

シェルコマンドの実行
ls -l の結果を得て表示する例
/**
 * Gjs
 */

import GLib from 'gi://GLib';

let output = GLib.spawn_command_line_sync('ls -l')[1];
let dec = new TextDecoder();
let text = dec.decode(output);
console.log(text);

/**
 * JXA
 */

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

// alteringLineEndings オプション無しだと改行が CR に変換される
let res = app.doShellScript('ls -l', {alteringLineEndings: false});
console.log(res);

/**
 * Node.js
 */

import {exec} from 'node:child_process';

exec('ls -l', (err, stdout, stderr)=> {
    console.log(stdout);
});

タイマー
setInterval, setTimeout は Gjs でも使えるようになりました
/**
 * Gjs (メインループが必要)
 */

import GLib from 'gi://GLib';

const mainloop = new GLib.MainLoop(null, false);

let count = 1;
let id = setInterval(()=> {
    if (count == 5) {
        clearInterval(id);
        mainloop.quit();
    }
    console.log(count);
    count++;
}, 1000);

mainloop.run();

/**
 * JXA
 */

ObjC.import('Cocoa');

let count = 1;
let operation = $.NSBlockOperation.blockOperationWithBlock(()=> {
    if (count == 5)
        timer.invalidate;
    console.log(count);
    count++;
});
let timer = $.NSTimer.timerWithTimeIntervalTargetSelectorUserInfoRepeats(
    1, operation, 'main', null, true);
$.NSRunLoop.currentRunLoop.addTimerForMode(timer, 'timer');
// 戻り値が表示されないよう変数に入れる
let r = $.NSRunLoop.currentRunLoop.runModeBeforeDate('timer', $.NSDate.distantFuture);

/**
 * Node.js (ブラウザと同じ、メインループは node がやってくれる)
 */

let count = 1;
let id = setInterval(()=> {
    if (count == 5) clearInterval(id);
    console.log(count);
    count++;
}, 1000);

Copyright(C) sasakima-nao All rights reserved 2002 --- 2024.