IniFileで日時を読み書きするときはグローバル変数FormatSettingsの値に注意する

環境はDelphi 10.1 Berlin。

TIniFileやTMemIniFileで日時を保存したり、保存した日時を読み込むには、WriteDate/WriteTime /WriteDateTime/ReadDate/ReadTime/ReadDateTimeメソッドを使用します。

問題は、これらのメソッドが内部で次の関数を使用していることです。

function System.SysUtils.DateToStr(const DateTime: TDateTime): string;
function System.SysUtils.TimeToStr(const DateTime: TDateTime): string;
function System.SysUtils.DateTimeToStr(const DateTime: TDateTime): string;
function System.SysUtils.StrToDate(const S: string): TDateTime;
function System.SysUtils.StrToTime(const S: string): TDateTime;
function System.SysUtils.StrToDateTime(const S: string): TDateTime;

いずれもグローバル変数FormatSettingsに依存しており、OSの言語・日時の設定が変わると、返値の値が変わります。
そのため、保存した日時を読み込めなくなる可能性があります。

TIniFileやTMemIniFileを使うときは、グローバル変数FormatSettingsの値に注意しておく必要があります。

上記の関数には引数にTFormatSettingsをとる関数も用意されています。
TIniFileやTMemIniFileを継承して日時を扱うメソッドをオーバーライドするのもいいかもしれません。

JavaScriptの既存オブジェクトを変更しない

メンテナブルJavaScript ―読みやすく保守しやすいJavaScriptコードのための作法』より。

Prototype JavaScript Framework(prototype.js)は、既存のオブジェクトに便利な機能を追加する人気のライブラリでした。
prototype.jsのdocument.getElementsByClassName()メソッドは、JavaScriptの標準仕様に取り込まれ、今では標準メソッドとして使用できます。

しかし、prototype.jsのdocument.getElementsByClassName()メソッドは配列を返すのに対し、標準JavaScriptはNodeListを返します。
そのためエラーが発生するようになりました。

既存のオブジェクトを変更すると、将来、予期しないトラブルに遭遇するかもしれません。
JavaScriptの既存オブジェクトを変更しないようにします。

よりよいアプローチとして、『メンテナブルJavaScript ―読みやすく保守しやすいJavaScriptコードのための作法』では、「ECMAScript 5のオブジェクトベース継承」・「型ベースの継承」・「Facadeパターン」を紹介しています。
安全でメンテナンスしやすいコードを書きましょう。

Androidのアプリ(apkファイル)を抽出して、証明書を確認する

Androidのアプリ(apkファイル)を抽出して、証明書を確認するメモ。

端末にインストールされているアプリ(apkファイル)を抽出する

Google Playでそのアプリのページを開き、URLからパッケージ名を確認します。
URLの「id=」に続く部分がパッケージ名になります。

https://play.google.com/store/apps/details?id=パッケージ名

パッケージ名を一覧表示

adb shell pm list packages -f

パッケージ名を絞り込んで表示

adb shell pm list packages -f | grep パッケージ名

「package:アプリの場所=パッケージ名」の形式で表示される

package:/data/app/~/base.apk=~

apkファイルを取り出す

C:\~\adb.exe pull /data/app/~/base.apk

証明書のfingerprintを表示する

keystoreから取り出す場合

keytool -list -keystore my-signing-key.keystore

apkから取り出す場合(Java7以上)

keytool -list -printcert -jarfile app.apk

Delphi 10.1 BerlinでファイルシステムにあわせてUnicode正規化する

ユニコードには結合文字というものが存在します。

たとえば「が」という文字の表現方法には、一文字で「が」と表現する方法と、「か」+「゛」のように濁点を結合する方法があります。
日本語以外でも結合文字は使われています。たとえばアクセント記号など。

結合文字を使った文字と結合文字を使わない文字が混在していると、検索にヒットしない(「が」と「か」+「゛」は別の文字)など、トラブルの元になりがち。
そこで一方に統一したい。

統一することをUnicode正規化といい、正規化には4種類の形式があります。
参考: Unicode正規化 – Wikipedia

  • NFD(正規化形式D)
  • NFC(正規化形式C)
  • NFKD(正規化形式KD)
  • NFKC(正規化形式KC)

ファイル名には、WindowsではNFCで正規化されます。
OSXではNFDをベースにした独自実装が用いられているようです。
正規化の方法が異なるため、WindowsとOSXでファイルをやりとりしたときに、意図せぬトラブルに遭遇するわけです。

Windowsでは、たとえばMECSUtils.MecsNormalize関数を使えば、NFCで正規化できます。

{$IFDEF MSWINDOWS}
uses MECSUtils;
{$ENDIF MSWINDOWS}

{$IFDEF MSWINDOWS}
function FileSystemStringForWin(const S: string): string;
var
  W: WideString;
begin
  if MECSUtils.MecsNormalize(S, W, NormalizationC) then
    Result := W
  else
    Result := S;
end;
{$ENDIF MSWINDOWS}

問題はOSXですが、CFStringGetFileSystemRepresentation関数を使えば、ファイルシステムと同じ変換ができるようです。
Delphi 10.1 Berlinには、System.SysUtilsユニットにStringToFileSystemString関数があります。
このStringToFileSystemString関数がCFStringGetFileSystemRepresentation関数を使った変換処理をしてくれます。

{$IFDEF MACOS}
function FileSystemStringForMac(const S: string): string;
const
  MAX_PATH = 1024;
var
  Bytes: TBytes;
  I: Integer;
begin
  SetLength(Bytes, MAX_PATH);
  if System.SysUtils.StringToFileSystemString(S, Bytes) then
  begin
    for I := 0 to Length(Bytes) - 1 do
      if Bytes[I] = 0 then
        Break;
    Result := TEncoding.UTF8.GetString(Bytes, 0, I);
  end;
end;
{$ENDIF MACOS}

OSの正規化の方法に合わせて文字列を変換するサンプルです。

function FileSystemString(const S: string): string;
begin
{$IFDEF MACOS}
  Result := FileSystemStringForMac(S);
{$ENDIF MACOS}
{$IFDEF MSWINDOWS}
  Result := FileSystemStringForWin(S);
{$ENDIF MSWINDOWS}
end;


procedure TForm1.FormCreate(Sender: TObject);
const
  S1 = #$304C; // が'
  S2 = #$304B + #$3099; // か゛
var
  C: Char;
begin
  for C in FileSystemString(S1) do
    Memo1.Lines.Add(C);
  for C in FileSystemString(S2) do
    Memo1.Lines.Add(C);
end;

Windowsでは濁点は結合されました。

win

OSXでは濁点は分割されました。

mac

JavaScriptからCSSを隔離する

ある要素のスタイルをJavaScriptで変更する必要があるとき、CSSクラスを操作するのが最善です。
メンテナブルJavaScript ―読みやすく保守しやすいJavaScriptコードのための作法』より。

Styleを使って複数のプロパティを変更している次のようなコードは悪い例です。

// Bad
element.style.color = "red";
element.style.left = "100px";
element.style.top = "100px";

良いコードは、CSSでクラスを定義しておき、要素に対してそのクラスを追加します。

// Good: Natice
element.className += "reveal";

// Good: HTML5
element.classList.add("reveal");

つい、手を抜いてしまい、JavaScriptでstyleを編集してしまうことがありますが、後で大変になります。
CSSに対して疎結合になるように、つねに意識したいです。

Delphi 10.1 Berlinでウィンドウのスケールを取得するには

Delphi 10.1 Berlinから、IFMXWindowService.GetWindowScaleは非推奨になりました。

これまでは次のようにして、ウィンドウのスケールを取得していました。

procedure TForm1.FormCreate(Sender: TObject);
var
ws: IFMXWindowService;
begin
if TPlatformServices.Current.SupportsPlatformService(IFMXWindowService, ws) then
    Memo1.Lines.Add('スケール:' + FloatToStr(ws.GetWindowScale(Self)));  
    //[DCC 警告] Unit1.pas(34): W1000 シンボル 'GetWindowScale' を使用することは推奨されていません
end;

この方法は警告が表示されるようになりました。

Delphi 10.1 BerlinからはフォームのハンドルのScaleプロパティを使用します。

procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Add('スケール:' + FloatToStr(Self.Handle.Scale));
end;

Simulator_Screen_Shot_2016

strictモードはグローバルスコープで指定しない

グローバルスコープに”use strict”を指定するのは一般には避けることが推奨されています。
メンテナブルJavaScript ―読みやすく保守しやすいJavaScriptコードのための作法』より。

“use strict”プラグマは、グローバルスコープだけでなく、関数の中でローカルに指定することができます。
既存のコードを修正する場合、strictモードをグローバルスコープで指定すると、既存のコードにstrictモードに対応していないコードがあるとエラーになってしまいます。

// Bad: グローバルstrictモード
"use strict";

関数ないで指定することで、strictモードの適用範囲を限定できます。

function doSomething() {
    "use strict";
    //コード
}

複数の関数にstrictモードを適用したいときは、即時関数呼び出しを使います。

(function() {
    "use strict";

    function doSomething() {
        // コード
    }

    function doSomethingElse() {
        // コード
    }
})();

グローバルなstrictモードをESLintで検査したい場合は、”rules”に次の設定を追加します。
(参考:Rule strict – ESLint – Pluggable JavaScript linter)

"strict": ["error", "function"]

Visual Studio CodeでESLintを使ったときの画面です。
グローバルなstrictモードをエラーとして検出しています。

eslint-global-strict-mode

メンテナブルJavaScript ―読みやすく保守しやすいJavaScriptコードのための作法』勉強になります。

Delphi 10.1 BerlinでDOSCommandコンポーネントを使う

TurboPackDOSCommandコンポーネントは、バッチファイルを実行し、出力された文字を受け取ることができるコンポーネントです。
VCLアプリケーションとFireMonkeyアプリケーションの両方で使用できます。
対応しているOSはWindowsのみです。

DOSCommand-2

GetItに登録されているので、簡単にインストールできます。

DOSCommand-1

コマンドラインを実行する

実行するコマンドラインをCommandLineプロパティに設定して、Executeメソッドでプロセスを実行します。

DosCommand1.CommandLine := 'C:\Users\yamamoto\Documents\dir.bat';
DosCommand1.Execute;

カレントディレクトリを設定するにはCurrentDirプロパティを使用します。

DosCommand1.CurrentDir := 'C:\Users\yamamoto\';

出力を取得する

OnNewCharイベントで新しく出力された文字を取得できます。

OnNewLineイベントで出力された行を取得できます。

procedure TForm1.DosCommand1NewLine(ASender: TObject; const ANewLine: string;
  AOutputType: TOutputType);
begin
  case AOutputType of
    otEntireLine:
      Memo1.Lines.Add(ANewLine);
  end;
end;

memoやricheditに出力を直接渡したいときは、OutputLinesプロパティを使います。

DosCommand1.OutputLnes := Memo1.Lines;

Linesプロパティでコマンドのすべての出力を取得できます。

Memo2.Lines.AddStrings(DosCommand1.Lines);

入力を送信する

SendLine関数でDOSプロセスに入力を送信できます。

DosCommand1.SendLine('', True);

プロセスを中止する

OnTerminatedイベントでコマンドの終了を取得できます。

procedure TForm1.DosCommand1Terminated(Sender: TObject);
begin
  Memo1.Lines.Add('終了しました。');
end;

プロセスが終わる前に止めたいときは、Stopメソッドを使います。
※未確認

X秒間実行した後にプロセスを止めたいときは、MaxTimeAfterBeginningプロパティを使います。
※未確認

X秒間出力がなければプロセスを止めたい場合は、MaxTimeAfterLastOutputプロパティを使います。
※未確認

その他の設定

Priorityプロパティでプロセスの優先度を変更できます。

uses Winapi.Windows;

DosCommand1.Priority := HIGH_PRIORITY_CLASS;

優先度には次の値を設定します。

  • HIGH_PRIORITY_CLASS
  • IDLE_PRIORITY_CLASS
  • NORMAL_PRIORITY_CLASS
  • REALTIME_PRIORITY_CLASS

TerminatedプロパティがTrueのとき、入力された文字を出力します。
※未確認

サンプルアプリケーション

フォームにTDosCommandコンポーネントと、TButtonコンポーネント、TMemoコンポーネントを配置します。

DOSCommand-3

Button1を押すと、カレントディレクトリを「C:\Users\yamamoto\」にして、バッチファイル「C:\Users\yamamoto\Documents\dir.bat」を実行します。

出力された内容ははMemo1に表示します。

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Clear;
  DosCommand1.CurrentDir := 'C:\Users\yamamoto\';
  DosCommand1.CommandLine := 'C:\Users\yamamoto\Documents\dir.bat';
  DosCommand1.Execute;
end;

procedure TForm1.DosCommand1NewLine(ASender: TObject; const ANewLine: string;
  AOutputType: TOutputType);
begin
  // 出力された文字を1行ずつ受け取る
  case AOutputType of
    otEntireLine:
      Memo1.Lines.Add(ANewLine);
  end;
end;

procedure TForm1.DosCommand1Terminated(Sender: TObject);
begin
  Memo1.Lines.Add('終了しました。');
end;

Visual Studio CodeでESLintを使う

eslintをインストールする

ESLintがインストールされていない場合は、コマンドプロンプトから次のコマンドを実行して、ESLintをインストールします。

npm install -g eslint

ESLintがインストールされているかは、次のコマンドを実行して確認できます。

eslint -v

インストールされていれば、ESLintのバージョンが表示されます。

VS Code ESLint extensionをインストールする

Visual Studio Codeで「Ctrl+P」を押します。

次のコマンドを入力し、Enterキーを押します。

ext install vscode-eslint

eslint01

eslint02

VS Code ESLint extensionがインストールされます。

VS Code ESLint extensionをインストールすると、設定ファイル(settings.json)の”eslint.enable”がtrueになります。

念のため、標準のJavaScriptの検証機能を無効にします。

メニューの「ファイル」→「基本設定」→「ユーザー設定」を選択します。

右側のウィンドウに「”javascript.validate.enable”: false」を追加します。

// 既定の設定を上書きするには、このファイル内に設定を挿入します
{
    // JavaScript の検証を有効/無効にします
    "javascript.validate.enable": false
}

eslintの設定ファイルを作成する

プロジェクトのフォルダーで、コマンドプロンプトから次のコマンドを実行します。

eslint --init

設定についての質問が始まります。
矢印キーで回答を選択して、Enterキーで確定します。

? How would you like to configure ESLint? (Use arrow keys)
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? No
? Where will your code run? Node
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Double
? What line endings do you use? Windows
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON
Successfully created .eslintrc.json file in C:\Users\yamam_000\Documents\sample

質問が終了すると、設定ファイルが作成されます。

ESLintで検証する

ここまで設定ができていれば、Visual Studio CodeはJavaScriptのコードをESLintで検証します。

JavaScriptファイルをVisual Studio Codeで開きます。
ESLintで検証されていることがわかります。

eslint03