我輩はブロガーではない。ネタもまだない

SASとかDelphiあたりの人様の役に立たないネタを提供します

mouse E10を買いました

mouse E10を買いました。
前回の楽天スーパーセールで購入しようと思ったのですが、購入期間が過ぎてしまったので公式サイトから購入。


Amazonだとスタイラスペンなしで、10日前くらいは12,661円で最安でしたが、今は16,000になっています。

さて、どうしてこれを買ったかというと、組み込みでディスプレイ付きのシステムを作ってほしいという依頼があったのですが、ラズパイ+液晶でやると、システムの基幹部分のコーディングをやり直さないといけないからです。
Windowsなら今手元にあるコードがほぼそのまま使えます(というヘボい理由)。

つまり、組み込みを一から作成するのではなく、タブレットPC+USBで組込基盤接続でどうですか?という話です。


というわけで、Windowsタブレット、できるだけ小さいもの、できれば安いもの、可能であれば防水あたり…
という条件で探していたところ、10インチと少し予定より大きめ(できれば7インチくらいが良かった)ですが、mouseコンピュータのE10を見つけたというわけです。

本当だったらPanasonicのTOUGHPADとかがいいんでしょうけど、値段がね…


というわけで、使ってみた感想を。

・本体容量について
セットアップ完了時点で空き領域は27.3GB/56.4GB およそ半分です。
本体になにかデータを保存するような使い方は推奨できませんね。
あっという間にいっぱいいっぱいになって、動作も緩慢になりそうです。

・性能について
現状使っている分には問題ありません。
付属のスタイラスペンもまぁ文字を書く分には全く支障ありません。
Apple Pencil等はつかったことないです)

・デザイン
持ちやすくて結構好きです。

キーパッド
磁石が弱いので、本体を持つと落ちるのがマイナスポイント。
打感はSurfaceのものよりたわみが少なく、いいと思います。


高性能なスペックを要求しないデスクトップアプリの検証には結構手軽でいいかも。
もう1台追加で買うかもしれません。

CDATA SAS Data Sets FireDAC Components の更新履歴

さて、なんだかバタバタしていて全く更新出来ませんでした。ネタも少しはあるんですが、うまく記事にまとめられず。

とりあえず、手元の開発で使用しているCDATAのSAS Data Sets FireDAC Components の更新履歴でも貼っつけておきます。
これは公式なものではなく、あくまでもリリース後(β版は除く)に私が報告した問題からまとめたものです。
公式できちんと出してくれればいいんですけどね。
今使ってるから、これから使われる方の参考になれば。(ダウンロードできるものが最新とも限らないので)

21.0.8109 : マニュアルの修正

  • update 等、対応していないコマンドの記述が存在する
  • VC++ (x86/x64) redistributable package が必要な旨がマニュアルにない

実はupdate, deleteには対応していません。SAS xptでも同様。未だにトップページでは使えるって言ってるの、結構問題だと思うんですけど。
(私は結局、テーブルをdropしてcreate & Insertで実装しました。)
また、V21U6から(21.0.6xxx?)VC++ (x86/x64) redistributable package が同梱されなくなったため、別途インストールが必要なのですが、この記載も追記されました。

21.0.8115 : テーブルの識別に関する修正

  • CREATE TABLE IF NOT EXISTS文がすでにテーブルが存在する場合にエラーを返す
  • CREATE TABLE直後に作成されたテーブルが識別されない

回避策として、Delphiでテーブルの存在チェックをする、一度FDConnectionを切断して再接続するという方法もあります。

21.0.8145 : 欠損値に関する修正

  • Nの変数に欠損値(NULL)が格納できない

結構致命的ですね。SASSASたらしめる「.」が格納できません。
このバージョンで以下のような感じで使えるように修正されました。

  create table if not exists nullcheck (id integer, text varchar(10));
  insert into nullcheck (id,text) values(null,'YYY'); 

21.0.8149 : 欠損値に関する修正

  • 指定しないNのカラムに欠損値が格納されず、0が格納されてしまう

以下のコードだと、idカラムに欠損値でなく、0が格納されてしまうという問題です。

  create table if not exists nullcheck (id integer, text varchar(10));
  insert into nullcheck (text) values('YYY'); 

21.0.8160 : 特定のデータセットの読み取りに関する修正

  • FDQueryでsortseq=linguistic()を指定したデータセットの1行目に空行が表示されてしまう

proc sortでsortseqなんてめったに使わないと思うんですが、何故か指定していたコードがあり、再現。
これは回避不可でした。

21.0.8163 : 特定のデータセットの読み取りに関する修正

  • FDQueryでsortseq=linguistic(case_first=lower NUMERIC_COLLATION=ON )を指定したデータセットの1行目に空行が表示されてしまう

sortseq=linguistic()の問題は前のビルドで対応出来ていましたが、さらにlinguisticにオプションを追加しても発生することが発覚。
もともとはsortseq=linguistic(case_first=lower NUMERIC_COLLATION=ON )で作成したデータセットで確認していたのですが、最小の再現コードがsortseq=linguistic()だったのです。

21.0.8165 : 小数の扱いに関する修正

  • Bulk Insertにおいて、カラム属性をAsFloatと指定した場合でにキャストエラーとなり、小数がデータセットに格納できない
  FDQuery1.SQL.Text:='create table if not exists floatcheck(id decimal(10,2),text varchar(10));';
  FDQuery1.ExecSQL;
  FDQuery1.Close;
  FDConnection1.ResourceOptions.ServerOutput := True;
  FDQuery1.SQL.Text := 'insert into floatcheck (id, text) values (:id, :text);';
  FDQuery1.Params.ArraySize:= 1;
  FDQuery1.ParamByName('id').DataType   := ftFloat;
  FDQuery1.ParamByName('id').AsFloats[i] := 12.3;
  FDQuery1.ParamByName('text').AsStrings[i] := 'AAAA';
  FDQuery1.Execute(FDQuery1.Params.ArraySize,0);
  FDConnection1.ResourceOptions.ServerOutput := False;
  FDQuery1.Close;

普通にInsert文ならOKでしたが、一気に挿入したかったので。

21.0.8179 : データセットに関する修正

  • 0obsのデータセットにInsertしてもSASで認識できない点を修正

Firedac側では正常にコマンドが終了し、select文でも行を正しく取得できるので問題を認識しづらかったと思います。
SAS側でデータを確認しようとすると、0オブザベーションですと表示されてしまいます。
欠損値のinsertチェックのときに発見しましたが、当時は状況が分からず後回しに。

以上です。

SASでWORDLE(その3:SAS/AF)

さてさて、SASでWORDLEというネタを引っ張ってきましたが、最後、SAS/AFでやるとどうなるでしょうか。
SAS/AFの場合、いわゆるアプリケーションとしてのFrameと、ちと古臭いProgram Screenがありますが、せっかくなのでProgram Screenでやってみたいと思います。

ソース付きカタログを以下に置いておくので、試したい方はどうぞ。SAS/AFのライセンスがなくても実行はできます。
https://bitbucket.org/t_kawakami/wordle_sas/src/master/


まず、任意のライブラリにカタログを作成し、Program Screenを新規作成します。
f:id:japelin:20220317004819p:plain


画面の構成はこんな感じです
f:id:japelin:20220317004926p:plain

白だと見づらいので、背景を黒くしています。
コマンドバーから

color background black

と実行します。

また、入力欄はわかりやすくするために、白の反転としています。
ここは、フィールド「&KWRD」を選択して同じ用にコマンドを

color mtext white r

と実行します。

また、文字は、選択後に

color mtext white

のコマンドで白くしています。

さて、次は一般属性です。

f:id:japelin:20220317005456p:plain
ツール>一般属性から以下のように指定します。

f:id:japelin:20220317005622p:plain
少なくとも、行数と列数を指定しないと、全画面になってしまいます。

一般属性を閉じたらツール>フィールド属性を開きます。

フィールド属性を下にスクロールすると、タイプ:ACTIONのフィールドが出てきます。
f:id:japelin:20220317005855p:plain
これは「&」のフィールドです。


入力した結果を表示したいので、保護=YES、オプションのCAPSをON、エイリアスは、配列で扱いたいので「RES+行+列」という名前に変更しておきます。
f:id:japelin:20220317010008p:plain

最後、ツール>ソースウィンドウでsclコードを書きます。
(ここでは、WORK.LISTの4obs目を使用するようになっています)

キモは、

  • 画面に入力した文字を表示するために配列として処理
  • fieldコマンドで処理したいので配列の内部変数名(resXX)を別の変数名(fieldname)にフィールド名として格納して処理
  • fieldコマンドで反転色を設定

あたりでしょうか。

max6回なので、6回以上できないように、また、成功したら入力欄を保護する、という処理も入れています。
ま、アプリケーションですから最低限の処理ということで。

length answer $5                                                              
       fieldname $5                                                           
;                                                                             
                                                                              
init:                                                                         
  control always label;                                                       
  rc=rc;                                                                      
  execcnt=0;                                                                  
  success=0;                                                                  
  resobs=4;                                                                   
  array res{30} $1 res11-res15                                                
                   res21-res25                                                
                   res31-res35                                                
                   res41-res45                                                
                   res51-res55                                                
                   res61-res65;                                               
                                                                              
  lid_answer=makelist(5);                                                     
  dsid=open('WORK.LIST','I');                                                 
  if dsid>0 then do;                                                          
    rc=fetchobs(dsid,resobs);                                                 
    if rc=0 then do;                                                          
      answer=getvarc(dsid,1);                                                 
      /*リストに保存*/                                                        
      do i=1 to 5;                                                            
        lid_answer=setitemc(lid_answer,substr(answer,i,1),i);                 
      end;                                                                    
    end;                                                                      
    rc=close(dsid);                                                           
  end;                                                                        
return;                                                                       
                                                                              
main:                                                                         
  if length(KWRD)=5 and execcnt<6 and success=0 then do;                      
    execcnt+1;                                                                
    success=1;                                                                
    do i=1 to 5;                                                              
      idx=SEARCHC(lid_answer,substr(upcase(KWRD),i,1));                       
      fieldname=cats('res',execcnt,i);                                        
      res{5*(execcnt-1)+i}=substr(upcase(KWRD),i,1);                          
      if idx=0 then do;                                                       
        rc=field('color grey r',fieldname);                                   
        success=0;                                                            
      end; else                                                               
      if idx=i then do;                                                       
        rc=field('color green r',fieldname);                                  
      end; else                                                               
      do;                                                                     
        rc=field('color yellow r',fieldname);                                 
        success=0;                                                            
      end;                                                                    
    end;                                                                      
    KWRD='';                                                                  
    if success then do;                                                       
      rc=field('protect','KWRD');                                             
    end;                                                                      
  end;                                                                        
return;                                                                       
                                                                              
KWRD:return;                                                                  
term:return;


最後に、実行>コンパイルしてエラーがないことを確認したら、実行します。
右クリックでもいいですし、以下のプログラムを実行してもOKです

proc display c=temp.wordle.wordle.program;
run;

こんなアプリケーションウィンドウが立ち上がりました。
f:id:japelin:20220317011654p:plain

入力するとこんな感じです。
f:id:japelin:20220317011713p:plain

SASでWORDLE(その2:UI追加)

SASでWORDLE - 我輩はブロガーではない。ネタもまだない
この追記です。

ベタですけど、%windowを使ってUIを作りました。

リストの作成のところは同じ

%let keychars=5;
%let resobs=4;
data list;
  length wordle $&keychars.;
  input wordle;
datalines;
PIZZA
QUICK
QUAKE
CRAZY
CHICK
WALTZ
;
run;


で、本題はここから。
%windowで入力欄を呼び出し、入力された内容をマクロ変数として、処理用のマクロに引き継いでいます(パラメータではないですけど)

%window wordleWindow
  color=gray
  icolumn=40
  irow=10
  rows=40
  columns=60
#1 @1 "Welcome to WORDLE in SAS"
#4 @8 "Enter 5 charactors and press Enter"
#6 @8 input 5 attr=underline REQUIRED=yes
;

%Macro Wordle;

  %display wordleWindow;  

  options nomprint nomlogic nonotes nosource;
  title ;
  %if %length(&input)^=&keychars %then %do;
  data _null_;
    file print;
    put 'not 5 charactors!';
  run;

  %end;%else
  %do;
    data res;
      %let success=1;
      set list(firstobs=&resobs obs=&resobs);
      length res1-res5 $2;
      %do i=1 %to &keychars;
        res&i=cats(char(upcase("&input"),&i),index(wordle,char(upcase("&input"),&i)));
      %end;
      keep res:;
    run;
  
    ods html;
    proc report data=res nowd noheader
      style(column)={FONT_SIZE=12pt WIDTH=20 TEXTALIGN=center FONT_WEIGHT=bold COLOR=white};
      column res: ;
      %do i=1 %to &keychars;
        compute res&i;
               if substr(res&i,2)="0"  then call define (_COL_,'style','style={background=gray}');
          else if substr(res&i,2)="&i" then call define (_COL_,'style','style={background=green}'); 
          else                              call define (_COL_,'style','style={background=D1C513}'); 
          res&i=substr(res&i,1,1);
        endcomp;
      %end;
    run;
  %end;
  %let input=;

%Mend;  

これで、

%Wordle

を実行するだけで以下のウィンドウが立ち上がります。



で、このように入力してenterを押せば…

結果が出力されます


後は6回の制限だったり、正解したら処理できなくしたり、といったエラー処理は…やめときましょう。

なお、残念ながらSAS Studioでは%Windowが動かないので使えません。

SASでWORDLE

TwitterのTLに流れてきたこのツイートで、おお、SASでWordleか、ということでやってみました。

(UIに関して追記:https://japelin.hatenablog.com/entry/2022/03/16/154301


WORDLEについてはググって試して見るのが一番かと思いますので、とりあえずコードを。


まずは、解答用のデータセットを作成しています。
と同時に、文字数を指定するkeycharと、resobsでどのobsを解答として採用するかを指定しています。
(実際にはresobsはランダムに割り当てればいいと思いますがとりあえず。)

%let keychars=5;
%let resobs=4;
data list;
  length wordle $&keychars.;
  input wordle;
datalines;
PIZZA
QUICK
QUAKE
CRAZY
CHICK
WALTZ
;
run;

続いてマクロです。
ちょっと力技ですが、文字+結果 という変数を作って、その結果からセルの色を変更し、結果だけを出力対象としています。

%Macro Wordle(input);
  options nomprint nomlogic nonotes;
  title ;
  %if %length(&input)^=&keychars %then %do;
    %put not &keychars charactors!;
  %end;%else
  %do;
    data res;
      set list(firstobs=&resobs obs=&resobs);
      %do i=1 %to &keychars;
        %let kw&i.=%substr(%upcase(&input),&i.,1);
        res&i=cats("&&kw&i",index(wordle,"&&kw&i"));
        keep res:;
      %end;
    run;
  
    ods html;
    proc report data=res nowd noheader
      style(column)={FONT_SIZE=12pt WIDTH=20 TEXTALIGN=center FONT_WEIGHT=bold COLOR=white};
      column res: ;
      %do i=1 %to &keychars;
        compute res&i;
               if substr(res&i,2)="0"  then call define (_COL_,'style','style={background=gray}');
          else if substr(res&i,2)="&i" then call define (_COL_,'style','style={background=green}'); 
          else                              call define (_COL_,'style','style={background=D1C513}'); 
          res&i=substr(res&i,1,1);
        endcomp;
      %end;
    run;
  %end;
%Mend Wordle;

で、例えば、

%Wordle(ABCDZ)

とすると、こんな結果。
f:id:japelin:20220316131533p:plain

%Wordle(CRZDA)

f:id:japelin:20220316131625p:plain

で、最後は

%Wordle(CRAZY)

f:id:japelin:20220316131657p:plain

こんな感じです。
宣言はしていないですが、ほぼarrayの書き方ですね。


普段ods全く使わないので、結果の区切り文字が消えればもっときれいだと思うんですが。



ただ、これだとインターフェースがないのがイマイチですかね。
%window使おうとしたら、マクロコンパイルの関係でうまく動かなかったので、他の方法で何かしらUI用意したいところです。

思ったより簡単だったThunderbirdのAdd-on開発

メーラーThunderbirdを使っています。
今はなきOutlookExpressからの移行を迫られた際に、以降の移行の容易さを評価して採用したのがきっかけです。
以来、ずっとThunderbirdを使っているのですが、ちょっとした不満があり、Add-onの開発をしてみました。

その不満というのは、認証キーの扱いです。
Thunderbirdに限ったことではないのですが、FortiClientVPNを使ってFortiGateとVPN接続する際、e-mailによる二要素認証を使っています。

この時、こんな感じのメールが飛んできます。

f:id:japelin:20220222160554p:plain

ですので、このメールを開いて(選択して)、認証キーをコピーして、FortiClientVPNのボックスに入力する、という作業が必要になります。


これを、メール受信したら勝手にクリップボードにコピーされると楽だなぁ、と考えたのです。

結果的に、2時間調べたくらいではメール受信時に自動処理というのは実現できなかったのですが、認証キーが入ったメールを選択すると、キーだけがクリップボードにコピーされるいうアドオンができました。

f:id:japelin:20220222161553p:plain


これでかなりVPN接続時の手間が減りました!



具体的にはこれだけ。

1. manifest.jsonを作成
2. 処理内容をjavascriptで作成
3. 2つのファイルを7-zipでzipにする
4. アドオンをファイルからインストールする

他で言われているようなプロファイルの作成とかは必要ありませんでした。
ソフトウェアはthunderbird 91と7-zipのみ。

エラー処理してない最低限のコードですけど、とりあえず以下で動きます。
権限も最小限に。

少ししたらzipそのものをダウンロードできるようにしたいと思います。
公式で配布するかどうかは…今のところは考えてません。


manifest.json

{
    "manifest_version": 2,
    "applications": {
        "gecko": {
            "id": "japelin-GetAuthCode@gmail.com",
            "strict_min_version": "91.0"
        }
    },
    "name": "GetAuthCode",
    "description": "Get Authcode From Mail",
    "version": "0.0.1",
    "background": {
        "scripts": ["background.js"]
    },
    "permissions": [
        "messagesRead",
        "clipboardWrite",
        "storage"
    ]
}


background.js

browser.mailTabs.onSelectedMessagesChanged.addListener(async (tab, messageList) => {
    for (let message of messageList.messages) {
        messagePart = await browser.messages.getFull(message.id);
        for (let hcontent of messagePart.headers['subject']) {
            var pos_sbj=hcontent.indexOf("AuthCode");
            if (pos_sbj == 0) {
                var pos_dlm=hcontent.indexOf(" ");
                var authcode=hcontent.slice(pos_dlm+1);
                navigator.clipboard.writeText(authcode);
            };
        };
    };
});


開発手順は後でまとめましょうかね。

SASのログを保存しつつ、目視チェックする

ちょっと本業が忙しくてなかなか記事更新ができないのですが、暇つぶしにやってる某掲示板の書き込みでも転載しておきます。
日本語での情報も需要があるかもしれませんので。

SASのログ、だいたい皆さん保存していると思いますが、実行時に同時に確認したいケースもあると思います。
バッチ実行ではログは特定のファイルとして出力され、通常の実行ではproc printlogをつかってログをファイルに出力すると、ログウィンドウに表示されませんよね。

そんなときの対策3つです。

続きを読む