dullwhaleのメモ帳

何度も同じことを調べなくてよいように...

治安の悪いgitリポジトリ内の文字コードをシェルからBOMなしUTF-8改行コードLFに統一する

他人から受け取ったファイルは文字エンコードShift_JISだったり、BOMがついていたり、改行コードがCRLFだったりする。 そのままファイルの編集を続けると、システムに繋げたり乗っけたりするタイミングで問題が発生しやすいから、BOMなしUTF-8 改行コードLFに統一したい。

通常の開発であればIDEやエディタに.editorconfigを追加すればよい。 ただし、そういった方法が取れない/取るまでもない状況もある。 そのような場合に使えるワンライナーを作ったからメモしておく。

事前にnkfコマンドのインストールが必要。

find -type f -not -path './.git/*' | xargs nkf --guess | grep -vE ": (BINARY|(ASCII|UTF-8) \(LF\))$" | rev | cut -d: -f2- | rev | xargs nkf -wLu --overwrite

revで2回反転させている箇所とか無駄が多いから、もっと効率の良い方法は考えられそう。 あまり大量のファイルを処理することは想定していない。

Debian12 bookwormにPythonをインストールしてグローバルに使うPythonパッケージをインストールする

この記事ではDebian 12にPythonをインストールし、コマンドのようにどこでも使うPythonパッケージをインストールする方法について記述する。

コマンドと解説

# Python本体とpipxをインストールする
sudo apt install python-is-python3 pipx
# パスを通す
pipx ensurepath
# 無事にインストールできたか確認
python -V

# 目的のPythonパッケージをインストール
pipx install ${PythonPackageName}

python-is-python3pythonを一々python3とタイプすることなく、pythonでも起動できるようにするパッケージである。 普通にPythonパッケージをaptからインストールすると、デフォルトでpython3でしか起動できない。 自分でシンボリックリンクエイリアスを設定してもよいが、既存パッケージがあるので活用する。 さらに、同パッケージは依存パッケージとしてpythonが設定されているから、Python本体のインストールも行われる。

pipxPython環境の隔離機能付きのパッケージマネージャーだと思えばいい。 詳細は公式のドキュメントを参照せよ。 わざわざpipxをインストールする目的はPEP 668の回避にある。

2023-01-30にリリースされたpipバージョン23.0以降、PEP 668に準拠した挙動が実装されている。 同バージョン以降のpipで仮想環境Pythonパッケージをインストールしようとすると、次のようなエラーメッセージが出力される。

error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.
    
    See /usr/share/doc/python3.11/README.venv for more information.

note: If you believe this is a mistake, please contact your Python installation 
or OS distribution provider. You can override this, at the risk of breaking your
 Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

PEP 668は簡単に言うと「グローバルのPythonパッケージと仮想環境内のPythonパッケージが衝突する問題などを避けるため、何らかの仮想環境内以外でpip installを行うな」と主張している。

cf. https://pip.pypa.io/en/stable/news/#v23-0

対応策として、エラーメッセージにもあるようにpipxが推奨されているから、これに従う。

it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. Make sure you have pipx installed.

このために、最初のsudo apt installで一緒にpipxをインストールしている。

なお、開発などプロジェクトでだけ使うパッケージについてはこれまで通りvenvなどを使えばよい。

ChromeだけPOSTしたデータが勝手にHTMLエンコードされる

Chromeは以下の全ての条件を満たすとき、>>> 勝手 <<<にPOSTデータをHTMLエンコードする。

  • HTML中に<meta charset="UTF-8" />が設定されていない
  • formの中身に非ASCII文字が含まれている
  • JS(JavaScript)を使わない、素のHTMLのsubmit

この動作はfirefoxでは発生しない。 いくら調べても規格上の動作としての記述が見当たらないから、おそらくChrome独自の仕様。

症状

ChromeでJSを使わないformを記述し、textareaに日本語を入れてボタンからsubmitでPOSTした。 するとサーバーに送られてきた文字列は二重にエンコードされていた。 最初にHTMLエンコードされ、その結果がさらにURLエンコード(パーセントエンコード)されていた。

格通りの動作なら、HTMLエンコードは無しでURLエンコードだけが行われるはずだ。 サーバーに送られてきたデータをURLデコードして元の文字列が得られない場合は、この問題を疑った方が良いかもしれない。

対策

ちゃんと以下を書いておく。

<meta charset="UTF-8" />

書き捨てるや、簡素なものだからといって省略すると痛い目に遭う。

無意味だったもの

fromのaccept-charsetをUTF-8に変更しても意味が無い。

<form action="/send" method="post" accept-charset="UTF-8">

小物を送る際に定形郵便よりも安いことがある日本郵政のミニレターを活用する

この情報は2024/04/02現在の情報である。 将来はミニレターが廃止されたり、値上げされたりしている可能性があるから利用前に料金を再確認せよ。

下記注意を読め

下記注意を読め

下記注意を読め

場合によってはかえって割高になることがある。

ミニレターの活用

封筒に入るものやカード類を送る際に最安の方法は封筒を使った定形郵便だろうか? 否。もっと安いミニレターというものがある。

表: 25 g以内の定形郵便とミニレターの料金の違い

郵便物 料金[円]
定形郵便(25 g以内) 84
ミニレター(25 g以内) 63

ミニレターは便せんと封筒が一体化したようなものになっている。 切手を張る必要はない。 自分で折り曲げ、糊付けする手間が必要になるものの、21円ケチれる。 サイズは小さく、あまり大きなものは入れられない。 画像検索などで確認せよ。

ミニレターの料金の注意

ミニレターは普通の郵便物として25 g以内のものを送る際には安いが、この条件から外れるとかえって割高になるから注意が必要である。 以下に頻度が高そうなパターンを記載する。

25 gを超える。

ミニレターではく、定形外郵便扱いになってしまう。 もし25 g < x ≤ 50 g で定形封筒に収まるなら、「50 gまでの定形郵便物」として出す方が安いかもしれない。 日本郵政のサイトで確認せよ。

オプションサービスをつける。

特定記録や速達などのオプションを付けると、既に切手が貼られているのに「追加で切手の料金 + オプション料金」が取られてしまう。 次の表で説明する。

表: 25 g以内の物を特定記録オプション付きで送る場合の定形郵便とミニレターの料金の差

送り方 封筒の料金[円] 通常の郵便料金[円] オプションの料金[円] 合計[円]
定形郵便 + 特定記録 0 84 160 244
ミニレター + 特定記録 63 63 160 286

表のとおり、ミニレターを使った方が合計料金が高くなっている。 定形封筒は厳密には0円でないが、差額の42円を上回るような封筒はまず買わないだろう。

未確認だが、速達や書留といったオプションでも同様の仕様だと考えられる。

国内の料金表(手紙・はがき) - 日本郵便

制限のある(E)SSIDに接続するとAndroidが「*はインターネットに接続できません。」と出ることを阻止する。

この記事の内容はまだ未検証で、調べたことをまとめただけであるから注意せよ。

Android端末は接続した(E)SSIDがインターネットに出られるか確認する仕組みがある。 特定のエンドポイントへ接続できない場合は、インターネットに接続されていないとみなし、「${SSID}はインターネットに接続できません。」というダイアログを出す。 さらに、周辺に利用可能な(E)SSIDがいる場合、勝手にそちらへ切り替えてしまう。

セキュリティ上の理由などから制限のあるネットワークを構築している場合、この動作が問題になることがある。 Androidが接続確認のために利用するエンドポイントはドキュメントによると以下のようだ。

  • connectivitycheck.android.com
  • connectivitycheck.gstatic.com
  • www.google.com

これらの接続を許可してやれば、問題が解決するかもしれない。(未確認)

Android Enterprise のネットワーク要件 - Android Enterprise ヘルプ

iOS端末も同様の検査の仕組みがあるようだ。

制限のあるネットワークを構築する必要がでたときに役立つかもしれない。

Windows App SDK(WinUI3)でXAMLから独自のプロパティを設定可能なUser Controlを作る

Windows App SDK(WinUI3)でコンポーネントをUser Controlとして作る - dullwhaleのメモ帳 の発展的な内容として、作成したUser ControlにXAMLから指定できるプロパティを実装する。

この記事ではUser ControlにXAMLからstringのプロパティと関数のプロパティを渡す部分を実装する。 また、実用性やUXよりも分かりやすさを優先している。 厳密には間違いである点が多々あるから、概要を掴んだらMSのドキュメントを見よ。

XAMLプロパティの制限

C#では関数をプリミティブ型の変数などと同等に扱わない。 C#の関数はfirst class objectだと説明されることがあるが、真に受けてはいけない。 これが原因で、プリミティブ型のプロパティの実装方法と関数のプロパティの実装方法が異なる。

DependencyPropertyとProperty wrapper

XAMLプロパティを機能させるためには、コード側でDependencyPropertyとProperty wrapperの実装が必要になる。 これらには命名規則や可視性の決まりがある。 変なこだわりを出すと機能しない可能性があることに注意せよ。 それぞれの要件は以下のように要約される。

表: User ControlでカスタムのXAMLプロパティを使えるよう、同User ControlのClassに実装すべきDependencyPropertyとProperty wrapperの要件

もの アクセス修飾子など 命名 役割
DependencyProperty DependencyProperty public static readonly ${XAML-property-name}Property XAMLのプロパティとして機能させるための既定のstaticメンバ
Property wrapper *
実装したい型
public ${XAML-property-name} C#から普通のClassプロパティかのようにアクセスするための上記のラッパー

単純な型のプロパティの例としてString型を実装する

ここで、最小限のサンプルコードを示す。 BazというUser ControlでSampleというプロパティを使えるよう、コードビハインドを実装する例を考える1。 つまり、XAMLで次のような記述ができるようにする。

<qux:Baz Sample="This is a test."/>

このとき、Sampleがプリミティブ型やそれに準ずる単純な型であるなら、基本の実装はそれぞれ次のようになる。 ここではString型とした。

using Microsoft.UI.Xaml;

// ----- 途中略 -----

// これがDependencyProperty
public static readonly DependencyProperty SampleProperty =
  DependencyProperty.Register(
    nameof(),
    typeof(Sample),
    typeof(Baz),
    null
  );

// これがProperty wrapper
public string Sample
{
  get { return (string)GetValue(SampleProperty); }
  set { SetValue(SampleProperty, (string)value); }
}

注意が必要な「関数」のプロパティを実装する

続いて、同User Controlに関数のプロパティProcedureを設定できるよう実装する例を考える。 XAMLでは次のような記述を想定する。

<qux:Baz Procedure="SomeFunctionImplementation"/>

関数のプロパティを実装するとき、それはイベントハンドラの追加を受け付けるプロパティを実装したことになる。 Property wrapperの違いに着目せよ。

using Microsoft.UI.Xaml;

// ----- 途中略 -----

public delegate void ProcedureType();

// これがDependencyProperty
public static readonly DependencyProperty ProcedureProperty =
  DependencyProperty.Register(
    nameof(),
    typeof(Procedure),
    typeof(Baz),
    null
  );

// これがProperty wrapper
public event ProcedureType Procedure;

予約語eventの意味が分からないなら、直ちにC#のイベントの機能について調べよ。 理解に必要な前提知識である。 自動で実装される暗黙のadd/removeアクセサーがあることにも注意せよ。

Property wrapperの実装例を見つつ、再度XAMLの記述に注目する。 このXAMLの記述の意味は、前の節のString型のものと打って変わって、「event source/senderであるProcedureにevent listener/handlerとしてSomeFunctionImplementationを追加する」となる。

<qux:Baz Procedure="SomeFunctionImplementation"/>

この仕様は恐らく、コントロールに関数を渡すシチュエーションがイベントハンドラを登録するぐらいしか無いからだろう。 関数とそれ以外の両方で同じXAMLの記述をするのにも関わらず、その意味が異なることが混乱の元である。

詳しいことはMSの公式ドキュメントを参照せよ。

Dependency properties overview - UWP applications | Microsoft Learn

この仕組みはwindows presentation foundation、universal windows platformでも利用されているから、WinUI 3ではない情報も参考になることがある。

もう少し実践的な実装例

ここでは、作成したUser Controlを利用するコードも含めたもう少しだけ実践的な実装例を示す。 分かりやすさを優先をしているため、これを参考にプロダクトのコードを書くことは避けるべきである。

Components/FormTextInput.xaml

<?xml version="1.0" encoding="utf-8"?>
<UserControl
    x:Class="foo.Components.FormTextInput"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:deimos.Components"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel Orientation="Vertical">
        <TextBox x:Name="InputTextBox" LostFocus="InputLostFocus" />
        <TextBlock x:Name="ErrorMessage"/>
    </StackPanel>
</UserControl>

Components/FormTextInput.xaml.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;

namespace foo.Components
{
  public sealed partial class FormTextInput : UserControl
  {

    // バリデーション行う関数の型
    // バリデーションに成功したらnull、失敗したらエラーメッセージをstringで返す
    public delegate string? ValidationFunction(string text);

    public FormTextInput()
    {
      this.InitializeComponent();
    }

    public static readonly DependencyProperty ValidatorProperty =
      DependencyProperty.Register(
        nameof(Validator),
        typeof(ValidationFunction),
        typeof(FormTextInput),
        null
      );
    public event ValidationFunction Validator;

    public static readonly DependencyProperty InitialMessageProperty =
      DependencyProperty.Register(
        nameof(InitialMessage),
        typeof(String),
        typeof(FormTextInput),
        null
      );
    public string InitialMessage
    {
      get { return (string)GetValue(InitialMessageProperty); }
      set {
        SetValue(InitialMessageProperty, (string)value);
        ErrorMessage.Text = value;
      }
    }

    // TextBoxがフォーカスを失った際に発火するイベントハンドラ
    private void InputLostFocus(object sender, RoutedEventArgs e)
    {
      if (Validator is null) return;
      var result = Validator(InputTextBox.Text);
      if (result == null)
      {
        ErrorMessage.Text = "";
        return;
      }
    }
  }
}

以下は、このUser Controlを使うPageのコード

BarPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<Page
    x:Class="foo.BarPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:u="using:foo.Components"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!-- 途中略 -->
        <u:FormTextInput InitialMessage="first message" Validator="TitleValidator"/>
<!-- 以下略 -->

BarPage.xaml.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace foo
{

  public sealed partial class BarPage : Page
  {
    public BarPage()
    {
      this.InitializeComponent();
    }

    public string? TitleValidator(string text)
    {
      return text.Length <= 85 ? null : "85文字以内で入力してください";
    }
  }
}

  1. bazquxともにfoobarに続くメタ変数である。

Windows App SDK(WinUI3)でコンポーネントをUser Controlとして作る

Webのフロントエンドのコンポーネントに相当するWinUI3のパーツはCustom ControlかUser Controlである。 Custom Controlはより低レベルの概念であり、独自の動作と描画ロジックを持てる。 対してUser Controlは既存のコントロール(UIパーツ)の組み合わせであり、フロントエンド用語のコンポーネントにより近いと思われる。

まずはUser Controlとして実装することを考え、User Controlで実現不可能な場合だけCustom Controlとして実装することを検討すると良い。 この記事では以降User Controlにだけ言及する。 また、実用性よりも分かりやすさを優先し、この記事では機能を抑えたUser Controlを作成する。

最低限のUser Controlを作成する

ここではStackPanel内にTextBoxとTextBlockを並べた塊をUser Controlとして作成する。 TextBoxに入力されている文字列が変化したことをイベントハンドラでキャッチし、TextBlockに反映する。

User Controlの実装

User Controlは名前通りWindows.UI.Xaml.Controls.UserControlというクラスを継承して実装する。 公式のドキュメントはこれ

UserControl クラス (Windows.UI.Xaml.Controls) - Windows UWP applications | Microsoft Learn

Visual Stdioの機能で作成する場合は「新しい項目の追加」ダイアログから「ユーザーコントロール(WinUI 3)」を選択する。 ここではComponents/ ディレクトリ配下に作成し、名前はFormTextInputとする。

生成された.xamlファイルと.xaml.csファイルを少し修正し、以下のようにする。

Components/FormTextInput.xaml

<?xml version="1.0" encoding="utf-8"?>
<UserControl
    x:Class="foo.Components.FormTextInput"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:foo.Components"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel Orientation="Vertical">
        <TextBox x:Name="InputTextBox" TextChanged="InputTextChanged" />
        <TextBlock x:Name="Message"/>
    </StackPanel>
</UserControl>

Components/FormTextInput.xaml.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace foo.Components
{
  public sealed partial class FormTextInput : UserControl
  {
    public FormTextInput()
    {
      this.InitializeComponent();
    }

    // TextBoxの文字が変更された際に発火するイベントハンドラ
    private void InputTextChanged(object sender, TextChangedEventArgs e)
    {
      Message.Text = InputTextBox.Text;
    }
  }
}

x:Nameを設定して、.xaml.cs側でインスタンスに名前でアクセスできるようにしている。 実用性はともかく、これでFormTextInputというUser Controlが作成できた。

作成したUser Controlの使い方

作成したUser Controlを、利用したいXAML中で名前空間としてインポートする。 例えば、以下に示すBarPageではxmlns:u="using:foo.Components"という記述より、u:で参照できるようになる。

補足説明:xmlnsについて xmlnsXMLにおける用語としての名前空間を定義する。 これはXAMLMicrosoft独特の仕様ではなく、本家のXMLの仕様である。

例えば次のように記述すると、localという名前空間が定義される。

xmlns:local="using:foo.Components"

すると、そのXMLでは接頭辞を用いてlocal:Bazのように参照できる。

当然、次のように記述すれば、a:Bazのように参照できる。

xmlns:a="using:foo.Components"

BarPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<Page
    x:Class="foo.BarPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:u="using:foo.Components"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!-- 途中略 -->
        <u:FormTextInput />
<!-- 以下略 -->

User Controlをもっと実用的に作る

この記事では単純なUser Controlを作成したが、実際のアプリケーションにおいてパーツとして再利用するためにはもう少し作りこみが必要だろう。 記事のスコープを狭めるため、発展的な内容については別の記事にメモする。