c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

OSXでGitの罠を回避しつつ更新されたPNGを圧縮するスクリプト

画像の圧縮自体はImageOptimと云ふプログラムをコマンドで呼んでゐる丈だ。お終い。
HomebrewではCaskroom/cask/imageoptimに在る。

そこでGitの話に成る。
利用シーンはご想像にお任せするが、gitでpullした後に、其のpullで更新されたPNGファイルをlosslessで最適化する。最適化するコードだけ抜き出すと以下に成る。

#!/usr/bin/env ruby
require 'shellwords'

targets = ...
system "ImageOptim #{Shellwords.shelljoin(targets)}"

targetsに指定すべきファイルを一覧するにはだうしたらいいだらうか。pullした時に更新したコミット番号がわかるので、diffをとり、削除された差分ではないファイルを一覧し、PNGを抽出する。簡単なシェルのスクリプトだ。git diff --name-statusを使へる。

#!/usr/bin/env ruby
require 'shellwords'

revisions = ARGV[1] || (print 'revisions> '; gets.strip)
targets = `git diff --name-status #{revisions}`.
  each_line.
  collect(&:strip).
  reject{|line| line.start_with?('D') }.
  collect{|line| line.sub(/\A\S+\s+/, '') }.
  select{|line| line.end_with?('.png') }
system "ImageOptim #{Shellwords.shelljoin(targets)}"

此れをImageOptimと云ふ名前で保存しておけば、./ImageOptim -- 64e88f6..2be0234等と呼び出せる。
餘談だが此の偽のコミット番号はruby -rsecurerandom -e"puts SecureRandom.uuid[0..6]"で作れる。
上で標準ライブラリのShellwordsを使ってゐるのは、空白を含んだファイル名が沢山転がってゐるからだ。そもそも此れが、bashスクリプトではなく面倒に成ってRubyを書いた理由だった。
次に現れたのは日本語ファイル名だった。OSXのgitはとうに日本語のファイル名を扱へるやうにはなってゐるが、親切にもエスケープして表示してくれる。\001\002…のやうな。
面倒だ。調べると此のエスケープをオフにするオプションがある。git config core.quotepathだ。此れをfalseにすればUnicodeのまま印字される。常にオフにするのも氣がひけるので、スクリプトの終了時に元の設定に戻すコードを書いた。Ruby的なブロック引數だ。

#!/usr/bin/env ruby
require 'shellwords'

def git_quotepath?
  case `git config --local core.quotepath`
  when ""        then nil
  when "false\n" then false
  when "true\n"  then true
  end
end

def set_git_quotepath config
  case config
  when nil
    `git config --local --unset core.quotepath`
  when false
    `git config --local core.quotepath false`
  when true
    `git config --local core.quotepath true`
  end
end

def without_git_quotepath &block
  config = git_quotepath?
  set_git_quotepath false
  block.call
ensure
  set_git_quotepath config
end

revisions = ARGV[1] || (print 'revisions> '; gets.strip)
targets = without_git_quotepath{ `git diff --name-status #{revisions}` }.
  each_line.
  collect(&:strip).
  reject{|line| line.start_with?('D') }.
  collect{|line| line.sub(/\A\S+\s+/, '') }.
  select{|line| line.end_with?('.png') }
system "ImageOptim #{Shellwords.shelljoin(targets)}"

# vim: set ft=ruby:

疲れた。

astyleを使ひdotnet/CodeFormatterがコンパイルできない環境でC#をlintする

題名で此の記事の内容は終はる。
私はVim (NeoVim) 使ひなので、IDEは殺す。IDEのコードフォーマッターはコマンドラインから呼び出せない限り殺す。Unity附属のMonoDevelopは殺す。
C#にはCodeFormatterと云ふ公式のコードフォーマッターが在る。此れを使ひたかったのだが、OSXコンパイルできなかった。
仕方が無いので、astyleと云ふコードフォーマッターを探してきた。

#!/bin/bash -eux
CodeFormatter="astyle --indent=tab=8 --indent=force-tab=4 --indent-cases --indent-namespaces --min-conditional-indent=0 --pad-oper --pad-header --delete-empty-lines --add-one-line-brackets --keep-one-line-blocks --lineend=linux --formatted"
find Assets/Scripts/ -name '*.cs' -exec $CodeFormatter {} \;
find Assets/Editor/ -name '*.cs' -exec $CodeFormatter {} \;
find Assets/ -name '*.cs.orig' | xargs rm
# vim:set ft=sh:

用は足せる。此れをCodeFormatterと云ふファイル名で保存して、./CodeFormatterとして使ってゐる。悲しい。
EditorConfigと組み合はせて使ってゐる。

root = true

[*.cs]
charset = utf-8
end_of_line = lf
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true

C#で指定した型の定數を返すジェネリック函數を書くのに式木を使ふ

小ネタを出してゆく。

C#で數値の定數を得たい。指定した型の數値の定數を得たい。即ち次の樣なインターフェイスを想定する。

using System;

public class ConstNumber
{
  public static void Main()
  {
    int one = GetOne<int>();
    Console.WriteLine(one);
  }

  private static N GetOne<N>()
  {
    // Something
  }
}

実際は或る場所でのデフォルト値として定數が欲しく、且つ外から型を指定できる抽象的なメソッドが欲しく、且つ此のメソッドの外では型安全でありたかった。
意外と此れはできない。

N GetOne<N>() { return (int) 1; }
GetOne<int>();
// => error CS0029: Cannot implicitly convert type `int' to `N'
N GetOne<N>() { return (N) 1; }
GetOne<int>();
// => error CS0030: Cannot convert type `int' to `N'
N GetOne<N>() { return (N) ((object) 1); }
GetOne<int>();
// => OK.
GetOne<long>();
// => System.InvalidCastException: Specified cast is not valid.

ジェネリクスの引數として渡された型がどの型判定するコードは直ぐに書ける。typeof(N)を確かめればよい。
然し指定した型の定數を作るコードが書けない。何故か。其んな函數は用意されてゐないやうだ。
C++では此れは簡単にできる。

#include<iostream>

template <typename N>
N getOne() {
  return (N) 1;
}

int main() {
  std::cout << getOne<char>() << std::endl
            << getOne<double>() << std::endl
            << getOne<float>() << std::endl
            << getOne<int>() << std::endl
            << getOne<long>() << std::endl
            << getOne<short>() << std::endl
            << getOne<unsigned>() << std::endl
            << getOne<unsigned char>() << std::endl
            << getOne<unsigned long>() << std::endl
            << getOne<unsigned short>() << std::endl;
}

何故か。C++ではコンパイル時に其々の型の函數の實體が、型變數Nをchar, double, float, int, ...に置き換へた函數の實體が作られるから、Nへのキャスト(N) 1はint等へのキャスト(int) 1に書き換へられてコンパイルされる。此れは普通のコードだ。
C#ジェネリクスは此う云ふものではない。型變數Nの情報は実行時にも取得できる。C++の樣に普通のコードに変換されてコンパイルされるのではなく、ジェネリクスそのものとしてコンパイルされる。
よく思ひ出せばC#にもコードを生成する機能は在った。わたしはUnity3Dを使ってゐたので新しい機能は無いが、式木Expression Treeは在った。此れを使ふ。

using System;
using System.Linq.Expressions;

public class ConstNumber
{
  public static void Main()
  {
    Console.WriteLine(GetOne<int>());
  }

  private static N GetOne<N>()
  {
    return Expression.Lambda<Func<N>>(
      Expression.ConvertChecked(
        Expression.Constant(1, typeof(int)),
        typeof(N)
      )
    ).Compile()();
  }
}

whereガードでNに参照型が渡されるのを禁止できる。

using System;
using System.Linq.Expressions;

public class ConstNumber
{
  public static void Main()
  {
    Console.WriteLine(GetOne<int>());
  }

  private static N GetOne<N>() where N : struct
  {
    return Expression.Lambda<Func<N>>(
      Expression.ConvertChecked(
        Expression.Constant(1, typeof(int)),
        typeof(N)
      )
    ).Compile()();
  }
}

intの1を作り、N型にキャストする。bool等を指定してキャストに失敗したらSystem.InvalidOperationException実行時エラーを吐く。式木は実行時に展開される。コンパイラは、兔に角此のコードがN型を返す事はわかるので、通す。実行時に1をintやfloatにキャストする普通のコードに展開され、実行される。
一部の數値型に限定したい等有れば、型變數Nをチェックするコードを書ける。

using System;
using System.Linq;
using System.Linq.Expressions;

public class ConstNumber
{
  public static void Main()
  {
    Console.WriteLine(GetOne<byte>());
    Console.WriteLine(GetOne<char>());
    Console.WriteLine(GetOne<decimal>());
    Console.WriteLine(GetOne<double>());
    Console.WriteLine(GetOne<short>());
    Console.WriteLine(GetOne<int>());
    Console.WriteLine(GetOne<long>());
    Console.WriteLine(GetOne<ushort>());
    Console.WriteLine(GetOne<uint>());
    Console.WriteLine(GetOne<ulong>());
    Console.WriteLine(GetOne<sbyte>());
    Console.WriteLine(GetOne<float>());
  }

  private static N GetOne<N>() where N : struct
  {
    GuardWithNumericType(typeof(N));
    return Expression.Lambda<Func<N>>(
      Expression.ConvertChecked(
        Expression.Constant(1, typeof(int)),
        typeof(N)
      )
    ).Compile()();
  }

  private static void GuardWithNumericType(Type t)
  {
    var supportedTypes = new TypeCode[] {
      TypeCode.Byte,
      TypeCode.Char,
      TypeCode.Decimal,
      TypeCode.Double,
      TypeCode.Int16,
      TypeCode.Int32,
      TypeCode.Int64,
      TypeCode.UInt16,
      TypeCode.UInt32,
      TypeCode.UInt64,
      TypeCode.SByte,
      TypeCode.Single,
    };
    if (!supportedTypes.Contains(Type.GetTypeCode(t)))
    {
      throw new NotImplementedException();
    }
  }
}

後は何でも。

殺したサーバーを一覧する

数秒で書き終はる鮮度系。
サービスには沢山の種類のサーバーがそれぞれ沢山たってゐ、sshから繋ぎ易いやうに .ssh/config に書いてあったりする。例へば此んなふうに。

Host momonga-web-01
  HostName x.x.x.x
Host momonga-web-02
  HostName x.x.x.x
Host momonga-db-01-01
  HostName x.x.x.x
…

然も此の一覧はスクリプトで自動でメンテされてゐたりする。
此れを前提として、やむごとなき理由でサーバーの台数を大幅に減らす事と成った。削減は人に遣っていただいたので、結果は聞けばよいのだが、聞いて正しい答が返ってくるとは必ずしも限らないので、予防として実地で確かめてみる。全サーバーにICMP pingを送り結果を一覧。時間は掛けられないので。

%ruby -e'File.read("#{ENV["HOME"]}/.ssh/config").scan(%r{^Host\s+(.+)\n\s+HostName\s+(.+)$}).uniq.sort_by{|m| m[0] }.inject({}){|table, m| table[m[0]] = m[1]; table }.select{|k, _v| k.include?("momonga") }.each{|name, ip| system("ping -c 1 #{ip}", out: "/dev/null", err: "/dev/null") ? puts("#{name} ok.") : puts("#{name} dead.") }'
momonga-web-01 ok.
momonga-web-02 dead.
momonga-db-01-01 ok.
…

書き捨てはRuby、置換は perl -pi0 -e's/…/…/g'、書き捨てでないスクリプトファイルはCrystalで書いてゐる。

Erlang/OTPのリリース番号をerlコマンドで確かめる

ネタは腐るほど溜まってるが腐ってるので氣力(

erl -noshell -eval 'io:format("~s~n", [erlang:system_info(otp_release)]), init:stop().'

erlang:system_info/1 を使ふ。
Serverspecだと

describe command(%{erl -noshell -eval 'io:format("~s~n", [erlang:system_info(otp_release)]), init:stop().'}) do
  its(:stdout) { should match(/18/) }
end

と書いてゐる。