Macで実行可能なFileMakerコード管理 CodeManager

FileMaker Proに集めたスクリプトのメモをそのまま実行環境にしてしまった話の続き、具体的にいきます。

前回のあらすじ

どこかで見つけたコマンドやスクリプトなどのコードをコピペしてメモっていると散在して探しにくくなってきたのでFileMakerで一元管理。一元管理だけに留まらず、簡単な小技を使うことでコマンド発射装置として育った…

実際に作ってみて手元にあるCodeManager.fmp12ファイルをネタにして具体的に行きます。以前ここに置いていたファイルを差し替え、このポストも散漫だったので全面的に書き換えました(2022年1月)

ファイル CodeManager 概要

このファイルの目的と機能は、コマンドやスクリプトなどコードをメモっておくことです。もう一つ、メモったコードを実行させます。現時点での完成ファイル、メインレイアウトはこんな感じです。

codeTemp.fmp12
メモ&実行装置のFileMaker書類(画像のファイル名が異なるのはご愛嬌)

 

主な機能とフィールド

メモやコードを書き溜める

真ん中あたりの青枠がメインで入力するフィールドです。codeTemp と名付けされてます。ここにメモやらコードを書き溜めます。書き溜めることがまず最初の目的で機能です。

他にタイトルのフィールド、フィルタするためのカテゴリーやタグといったフィールドがあります。

メモったコードを実行する

実行ボタン

codeTempフィールドに書いたコードを実行させます。流れはこうです。

1 テンプレを書く -> 2 値を書く -> 3 コードを生成(自動) -> 4 実行させる

実行を担当するのはスクリプトステップ「AppleScriptを実行」です。ですのでこの投稿はMac版FileMakerのお話になります。当ブログは基本Macの話しかしていません。すいません。

単純なコードならいいのですが、ファイルパスや名前などのパラメータが含まれることも多いですね。

codeTempではパスなどのパラメータ部分にあらかじめ決めておいた文字列を仮置きしておくという厳格なルールを課しています。

実際の値を別のパラメータ用フィールドに書きます。テンプレートの特定文字列とパラメータ用フィールドの内容を置換することで最終的なコードを完成させ、それを実行させます。

codeTempフィールドではパラメータの入る箇所に特定文字列を含め(ここでは $POSIX1)実際の値を右側に入力することで、実行するコード(下段のcodeFinal)が作られる

パラメータ用の文字列とフィールドについて詳細は後述します。パスやタイムスタンプなど種類別に8個のフィールドと、対応する置き換え文字があります。

手順を機能とフィールドで説明するとこうなります。

1 テンプレフィールド(メモとコードのテキストフィールド)
2 値フィールド(パラメータの実際の値)
3 完成コードのフィールド(計算フィールド)
4 実行スクリプト(スクリプト)

パラメータがない場合は の内容がそのまま 3 になります。いずれにしても 4 実行 で実行させる対象は 3 になります。

コードを実行することに関しては以上ですが、続きがあります。実行した後です。

実行してお仕舞いっていうコードならここまででOKですが、実際はもうちょっと複雑で「何かを取得する」コードというものがあります。ファイルの日付を受け取るとか、画像のメタデータを取得するとか、そういうやつです。是非とも結果を受け取りたいです。ですのでコード実行の上記に加えて次の工程とフィールドが外せません。

5 結果を受け取る(結果フィールド)

「結果フィールド」というフィールドがあって、そこに結果を受け取ります。結果を受け取ることについての詳細は、上記4つの話の後で。

 

CodeManagerファイルの概要は以上です。


ファイルはこちら。ダウンロードして試せます。このポストの内容からしてお分かりでしょうが、完全にMac専用 です。


Download CodeManager.zip

ファイルは永遠の未完成。時々アップデートがあると思います。
不出来な箇所を発見されたら教えてください。

ダウンロードするとファイルが二つ入ってます。ひとつはLite版で、入力の便利機能をそぎ落としたもの。他のFileMaker書類にコピペで組み込むことも容易です。

フル機能のほうは、入力補助やオマケ機能が付いてます。テーブルも多いです。

一部MacのショートカットをAppleScriptで利用しています。使用しているキーボードの種類によってキーコードが変わります。USキーボードでない方は、スクリプト内のキーコードを変更していただかなければなりません(後述)


ということで、ここからは技術的な細かな話になります。

コードを実行させる機能

手順と機能とフィールドを上で書きました。

1 テンプレフィールド(メモとコードのテキストフィールド)
2 値フィールド(実行させる時の実際の値)
3 完成コードのフィールド(計算フィールド)
4 実行スクリプト(スクリプト)

この4つを見出しに、詳細行きます。

1 テンプレフィールド – codeTemp

メインであるメモやコードを書き記し溜め込んでいくフィールドです。意味的にはメモやコードを書き記し溜め込んでいきます。技術的にはメモやコードを書き記すテキストフィールドです。手順的には「新規レコード」のあと、メモやコードを書き記し溜め込みます。

ただし一つ厳格なルールがあります。

変数の仮置き文言のルール

コードにパラメータが必要であれば「ここにパラメータ置きますよ」の印を付けます。パスであるべき箇所なら $POSIX1 、コード内に2個目のパスを書く箇所があれば $POSIX2と書きます。タイムスタンプであるべき箇所では$TIMESTAMP1を書くといった案配です。

このルールは厳密かつ具体的で、以下の8個のみを想定しています。

$POSIX1
$POSIX2
$NAME1
$NAME2
$TIMESTAMP1
$TIMESTAMP2
$TIME1
$TIME2

厳密である理由は、変数のそれぞれに完全対応するパラメータを入力するフィールドがあるんです。逆に言えば、パラメータのフィールドたちに合わせて$POSIX1などといった仮置きの文言があります。

 

[ オマケ ] 入力補助

仮置きの変数文字列は絶対なので、入力欄にカーソルがあるときにボタンで入力する仕組みまで作ってます。

codeTemp 入力補助
入力補助

また、トリガに仕込んでカラーリングも施しています。

みんな大好きSubstituteを使ったカラーリングスクリプト

codeTempフィールドについてはまだ書くことがありますが一旦ここで切って次の話に進みます。

2 値のフィールド – param_XX

パラメータのフィールドです。実際の値を書き入れます。posixにはパスが、timestampにはタイムスタンプが入ることが望ましいフィールドです。望ましいだけで、なるべくそうしましょうということです。技術的な理由はありません。「ぱっと見てどういうパラメータの値なのかが分かりやすいから」程度のことです。

パラメータフィールドのフィールド定義
パラメータフィールドのフィールド定義

8個であることが適正かどうかわかりません。今のところ余りこそすれ不足したことはありません。こういう仕組みはあまりスマートと言えないし、将来ひっくり返るかもしれませんが現状ではこのようになっています。

パラメータの中に「FM」がありますが、これはパスをFileMaker形式に変換する自動計算のフィールドで、ファイルによって使用することがあります(つまりこのCodeManager、他のFMファイルでも使い回してるんです)

パラメータフィールドがグローバルフィールドである点についてですが、必ずしもグローバルである必要はないと思います。レコードごとに「代表的なパラメータ」として保存されるのも悪くない。でもあえてグローバルにしてレコードに保存されないようにしています。パラメータはその都度使うだけのはかない存在なのです。

 

[ オマケ ] 履歴の機能

param_XX のパラメータフィールドに入力しやすくするため、多く機能を詰め込んでしまってます。そのひとつが履歴からの入力です。

履歴テーブル
履歴テーブル

履歴テーブルにパラメータフィールドで決定した履歴を残し、ポップアップで選択して再度の入力が簡単にできます。posixならパス、timestampならタイムスタンプです。変数の代用語やフィールド名をposixやtimestampで分けた理由のひとつがここにあります。

ポップアップ
POSIXではパスがポップアップされる
ポップアップ 名前
NAMEでは名前がポップアップされる

逆に言うと、たったこれだけのためにフィールド定義や仕組みを煩雑なものにしてしまったと言えなくないかもしれません。

あとついでに、パスである場合は「フォルダを選択」「ファイルを選択」ボタンでダイアログから選択できるようにもしています。フォルダ選択はスクリプトステップにあるけど、ファイルを選択ってどうやると思います?無理矢理なやりかたで実現しています。

 

パラメータの値は、3 完成コードのフィールド(計算フィールド)で置換するためにあります。

3 完成コードのフィールド – codeFinal

実行するコードを完成させる計算フィールド codeFinal です。メインのcodeTempフィールドに書かれたテンプレコードの、変数の代用語($POSIX1とか)を実際のparamの値に置き換えます。置き換えは完全に1対1の関係ですから単純です。

Substitute ( CodeTemp ;
 [ "$POSIX2" ; param_POSIX2 ] ;
 [ "$POSIX1" ; param_POSIX1 ] ;
 [ "$POSIX" ; param_POSIX1 ] ;
 [ "$NAME2" ; param_NAME2 ] ;
 [ "$NAME1" ; param_NAME1 ] ;
 [ "$NAME" ; param_NAME1 ] ;
 [ "$TIMESTAMP2" ; param_TIMESTAMP2 ] ;
 [ "$TIMESTAMP1" ; param_TIMESTAMP1 ] ;
 [ "$TIME2" ; param_TIME2 ] ;
 [ "$TIME1" ; param_TIME1 ] 
)

「4 実行させる」とき、その対象は常にこの codeFinal フィールドです。パラメータを必要としないコードであっても同じ処理です。パラメータがなければ何も変換されないだけですから。

例えば、codeTempフィールドにこうあって、

exiftool $POSIX1

param_posix1 フィールドに「target.jpg」と入力されれば、codeFinal が

exiftool target.jpg

こう仕上げます。

codeManager画面
codeTempとparamフィールドとcodeFinal、基本的な置換のかたち

 

4 実行スクリプト

手順と機能とフィールドに沿って話をしています。

1 テンプレフィールド(メモとコードのテキストフィールド)
2 値フィールド(実行させる時の実際の値)
3 完成コードのフィールド(計算フィールド)
4 実行スクリプト(スクリプト)

いよいよ、4 実行スクリプト 行きます。

すでに述べたように、AppleScriptを実行スクリプトステップを使います。が、codeTempに書かれたコードは AppleScript だけではありません。ターミナルのコマンドというかシェルスクリプトと言うんですか?そういうのも当然あります。

実行スクリプトが複数必要になります。

素で実行

codeTempフィールド内容がAppleScriptのスクリプトの文であるなら、「AppleScriptを実行」でそのまま実行します。

AppleScript
codeTempがAppleScriptの文なら
AppleScriptのメモ
codeFinalフィールドをそのまま実行

スクリプトステップ「AppleScriptを実行」で codeFinal フィールドを指定すればいいだけです。

codeFinalフィールドを指定するだけ

コピペで実行

ターミナルコマンドの場合はどうしましょう。どうにかします。問題ありません。

codeRun 画面
ターミナルのコマンド

コピペで実行するんです。

FileMakerでコード管理を作る前はどうしてましたか。メモ帳のコードをターミナルウインドウにコピペしていました。同じことをするだけです。

手動でやっても大した手間ではないですがそれだと今までの不便と変わりません。これをAppleScriptで書いてファイルメーカーのスクリプトに作っておき「コピペで実行」ボタンに割り当てると一発で実行できます。

codeFinalをコピーしてから、ステップ「AppleScriptを実行」で以下のAppleScriptを指定します。

tell application "Terminal"
	activate
	tell application "System Events"
		key code 45 using {command down}
		key code 9 using {command down}
		key code 36 using {command down}
	end tell
end tell

ターミナルをアクティブにして新規ウインドウしてペーストしてリターンする AppleScript です。というか、コマンドN、コマンドV、リターンのキーストロークを実行させているだけのアホみたいなスクリプトですね。でもこれで期待通りの動きをします。

※ リターンキーのキーコードはキーボード配列によって異なるようです。36はUSキーボードの場合。JISなら・・・ 何番だっけ? キーコードを調べるアプリで調べて、ここを修正してください。→ キーコードを調べるアプリ

キーコードがわかれば AppleScript の知識がなくてもショートカットを並べるだけでいろんな操作が簡単に自動化できますよ。

 

do script

余談になるかもしれませんが、コピペ実行の他、do script で書くという方法もあります。これは難易度が高くて素人が手を出すのは難しい。でも毛が生えた程度の素人に育つとちょっと使えるようになります。

エラーをなくすための素人の防衛としては、AppleScriptの中でターミナルを呼び出して do script を使うことです。直接シェルコマンドを送り込もうと考える人はすでに猛者なので放っておきます。

tell application "Terminal"
	do script "ここにコマンド"
end tell

基本、こう書きます。AppleScriptからターミナルを呼び出し、そこに do script “コマンド” を書きます。安全に実行できます。

実行スクリプトで使用しない

みんな大好き置換のsubstituteを駆使して計算式で do script の形に変換することを試みました。

しかし、変換しようとしている元のシェルスクリプトは「ここにコマンド」で済むような単純なコードは少なくて、パスやオプションやパイプや ” ” や ‘ ‘ や > やいろんな要素が入り込んでいます。

実行スクリプトの賢いバージョンになる筈でしたが、結果諦めました。理解力もまったく足りなかったし。実行スクリプトの計算式で関数を駆使するよりも、そもそもcodeTempフィールドにdo scriptごと書けばいいじゃん。となりました。

do script - codeTemp
do script を codeTemp に書けばいい

結論として実行スクリプトは素で実行コピペで実行の二つあればいいということです。

 

さてそんなわけで実行スクリプトが二種類できました。で、素で実行コピペで実行の二つの実行スクリプトをそれぞれボタンに割り当てて画面に並べ、コードの内容に応じてどっちかのボタンを押せという、そんなレイアウトにしますか。そんなの厭です。何が悲しゅうて当たりボタン探しをせねばならんのか。

二つの実行スクリプトを統合して真の実行スクリプトを作ります。codeTempに書かれた内容がAppleScriptなら素で実行、Terminalのスクリプトならコピペ実行と振り分けます。

これで真の実行スクリプトができました。

実行スクリプトの条件分岐

人間が二つのボタンを選ぶとすれば、コード内容の何を見ますか。それと同じことをスクリプトの条件分岐に書き、二つの実行スクリプトを振り分けます。

数少ないサンプルを見渡したところ、AppleScriptで書かれたコードは、do script も含め「tell 」または変数宣言の「set 」で始まっています。そして「end tell」で終わっています。これらの特徴を持っていれば、まず間違いなくAppleScript と分かります。でも例外もありました。もう少し特徴をつかんで、何とかAppleScriptであることを明確にします。

ターミナルのシェルスクリプトにはAppleScriptほど明確な特徴はないですが、間違いなく「end tell」で終わっていませんし、要するにつまり「AppleScriptの特徴を持っていれば素で実行、そうでなければコピぺ実行」という分岐を作れると思われました。

でも「それ以外」にしてしまうと実行する気のないメモやURLまでもが引っかかります。もうちょっと賢く条件分岐できないでしょうか。

自動カテゴリー

そこで役に立つのがカテゴリーという分類です。カテゴリーは人力で付与していくものというイメージがありますが、codeTempに書かれたテキストから自動で判断してレコードをカテゴライズするという手を使いました。

細かい条件は省略しますが、そんなわけで「カテゴリー自動計算」を行うことで「do Script」「AppleScript」「Terminal」「memo」とカテゴリー分類することができました。

実行スクリプトに戻って、条件分岐を「カテゴリーがAppleScriptまたはdoScriptなら素で実行、Terminalならコピペ実行」と振り分けます。これにて解決。

ということで、最初に挙げた4つの話は以上です。

1 テンプレフィールド(メモとコードのテキストフィールド)
2 値フィールド(実行させる時の実際の値)
3 完成コードのフィールド(計算フィールド)
4 実行スクリプト(スクリプト)

冒頭で書いたとおり、まだ続きがあります。

5 結果を受け取る(結果フィールド)

codeTempに書くコードは実行するだけで終わりではなくフィードバックを得るところまで含みます。

例えばファイルの日付を取得するコードでは日付を受け取ります。「結果フィールド」という、結果を受け取るフィールドを作りました。

出力を指定するコード

どうやって受け取るか。を司るのはもちろん codeTemp に書くコードそのものです。コードを実行した結果の出力を指定します。

考えられる方法は三つ。FileMakerのフィールドに直接出力するか、ファイルに保存するか、クリップボードに格納するかです。

この件について、くどくど説明し始めるとまた長くなるので適当に済ませますが、まず「結果をFileMakerのフィールドに直接出力」はAppleScriptなら可能ですがややこしいのでパスしておきましょう。以前はずっとこれを使っていましたが。こんな風に書きます。

tell application "FileMakerの名前"
activate
set contents of field "結果フィールド"  of current record to 前段で仕込んだ結果の変数 as string
end tell

クリップボードへ出力

現在、自分のレコードの話をすると、AppleScriptだろうとシェルスクリプトだろうと、結果を取得するコードではすべてクリップボードに出力しています。そしてクリップボードの内容を結果を受け取るフィールドにペーストします。最後スクリプトステップ「貼り付け」するだけですから楽です。

クリップボードへの出力は、AppleScriptだとこんなかたち。

set the clipboard to 仕込んだ変数

ターミナルでは | で繋げて pbcopy です(エラー出力はちょっと別の繋げ方)

コマンド | pbcopy

 

ところでここだけの話、これを書いている私の正体はドンバで絵描きの理系要素0のド素人です。FileMakerのスクリプトステップには「Event を送信」というのもありまして、オプションに「結果をクリップボードへコピー」という項目があります。これを上手く使えば何か良いことが起きそうな気はしているのですが、「Eventを送信」が何をすれば何ができる何であるのか今でもさっぱり分かりません。ずっと見てみぬ振りをしてきましたが、最近ちょっと気になっています。「Event を送信」を理解する日が来るでしょうか。教えて智恵子抄。

結果をペースト

さてクリップボードに出力があることを前提に、結果をペーストするスクリプトを作ります。

基本スクリプトステップ「貼り付け」で結果フィールドを指定するだけです。ただしいろいろあります。

結果フィールド

結果フィールドをグローバルにしています。どんなテーブルであっても等しく結果を受け取れるからです。また、大抵の場合結果は「一時的な結果」に過ぎないからです。グローバルな結果フィールドは、コード実行を使う可能性があるすべてのレイアウトに配置します。画面外の見えないところでOK。「貼り付け」ステップはレイアウト上にフィールドがないと動きません。

結果の待ち時間

結構大事なポイントです。実行するコードによって、処理時間が変わります。例えば一つのファイルの作成日付を取得するのは一瞬です。しかし「フォルダ以下サブフォルダを含むすべてのファイルのメタデータを取得」みたいなのは処理が終わってクリップボードに出力されるまでに大層時間がかかります。

スクリプトでコード実行から結果ペーストまでを繋げてしまうと、処理が終わってない状態でクリップボードの内容をペーストしてしまって意味不明な結果に終わります。

これを回避するための秘策はループを使って小刻みにペーストを繰り返すことです。別のどこかで同じ仕組みを説明したことがありますが、流れはこうです。

事前に結果フィールドを空にしておく
その空の結果フィールドの内容を変数 $結果 に保存しておく

コードを実行させる(出力がクリップボードのコード)

Loop
  少し待ってから(一時停止.5秒とか)
  結果フィールドにペースト
  Exit loop if [ 結果フィールド ≠ $結果 ](異なればループ終わり)
   (出力がまだなら、結果フィールド = $結果 だから)
   (出力が完了なら、結果フィールド ≠ $結果 だから)
end Loop

実際には、ループをカウントし、一定数繰り返しても駄目ならエラーの可能性があるからキャンセルダイアログを用意しておくなどの措置を講じます。

待ち時間を多めに取ったロング待ちバージョンのスクリプトを作っておいて、実行するコードによって分けてもいいでしょう。

この「結果をペースト」スクリプトと先ほどの「コード実行」スクリプトを組み合わせて「コード実行から結果取得までのスクリプト」としてまとめます。

以上、結果をペーストするスクリプトでした。

結果を得た後どのように利用するかは codeManager の知るところではありません。別のFMファイルで活用します。

CodeManager でのコード実行に一手間かけたというお話

このCodeManagerというファイル、最初は「コードのメモ、オマケで実行もできる」というだけのものでした。でも案配が良いのでいろんなFileMakerのファイルに組み込んで使うようになり、「汎用的なAppleScriptやシェルスクリプトの実行アドオン」みたいになってきました。

その使い方だとコードを呼び出すのが面倒なので、セルフリレーションを組んでグローバルな仮IDの入力によって目的レコードを呼び出す形になりました。

その流れから、実行するにはまず「セット」でグローバルIDに目的レコードのIDを入力してから、同じくグローバルなcodeFinal(copy)を実行するという手順となってます。

ファイルを使ってみて、いちいち「セット」て何のことやねんと不審に思われるかもしれないので書いときました。

 


ということで、本稿はとりあえずここで一旦締めます。

ファイルはこちらです。ダウンロードして試せます。


Download CodeManager.zip

ファイルは修正されたり更新されて再アップされる可能性があります。ファイルの不出来や失敗箇所を見つけたら教えてください。キーコードにご注意ください。

 

コメント

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください