PowerShell Core + イベント管理

これまで2002年より、Sakura-Conのゲストリレーションでスタッフをするにあたり、数々の技術を使用し、イベント計画などを立ててきました。過去には、Org-modeや小型のPythonスクリプトなどを使用してきましたが、今年は、PowerShell Coreを使用し、システムを構築しました。これまでに、PowerShellを使用して、多岐にわたるシステムを構築し、例えばカラオケ歌唱データの集計などがその一例で、他の領域での使用も検討していました。

目的

今回の目的としては、中核的なソリューションとして次を検討しました。

  • イベントスケジュールデータの集計と、可用性を高めたデータの作成。
  • カレンダーデータなどへの他の有用なデータ形式への変換の提供。

PowerShell Coreを選択した理由は以下のような理由からです。

  • LinuxとWindowsを使用している関係上、複数のプラットフォームで動作できる環境が重要でした。Androidで動けばなおプラスですが、この点に関してはUserLAndなどの環境を使用することで可能でした。(尚、Androidで使用する場合は、このようなキーボードの使用がおすすめです。
  • オフラインで使用可能なこと。イベント会場などではインターネット接続が劣悪になることがあり、インターネット接続なしでも使用できることが重要でした。

構成と内容

最初に作成したモジュールはスケジュールシステムより、データを取得するためのシステムでした。スケジュールシステムは、データを取得するためのAPIを持っておらず、この部分に関してはSeleniumを使用し、データを抽出しました。

Seleniumを使用したデータの抽出(守秘事項のため、使用しているソリューション名に関しては伏せてあります。)

Seleniumを使用してデータを取得するのには800件のエントリーで約30秒を要します。(実際に使用するデータはその8分の1程度。)SeleniumモジュールにおいてはUI上で必要な情報が存在している場所まで移動し、結果をDOMより抽出するようなものです。これはC#でプログラムしました。SeleniumはLinuxとWindowsでドライバが提供されているため、これはクロスプラットフォーム動作が可能です。(こちらは、Androidでは動きませんが、Androidで動作するドライバと対応するブラウザがあれば恐らく可能です。)

このシステムは以下のようなデータ構造を列として出力します。

   TypeName: SakuraCon.Relations.Data.Event
Name        MemberType     Definition
----        ----------     ----------
End Time    AliasProperty  End Time = EndTime
Event Title AliasProperty  Event Title = EventTitle
Start Time  AliasProperty  Start Time = StartTime
Equals      Method         bool Equals(System.Object obj)
GetHashCode Method         int GetHashCode()
GetType     Method         type GetType()
ToString    Method         string ToString()
EndTime     Property       datetime EndTime {get;set;}
EventId     Property       string EventId {get;set;}
EventTitle  Property       string EventTitle {get;set;}
Notes       Property       string Notes {get;set;}
Rating      Property       string Rating {get;set;}
StartTime   Property       datetime StartTime {get;set;}
Type        Property       string Type {get;set;}
Venue       Property       string Venue {get;set;}
Duration    ScriptProperty System.Object Duration {get=($this.EndTime - $this.StartTime);}

これに加え、以下のようなps1xmlを定義しました。

<Types>
    <Type>
        <Name>SakuraCon.Relations.Data.Event</Name>
        <Members>
            <MemberSet>
                <Name>PSStandardMembers</Name>
                <Members>
                    <PropertySet>
                        <Name>DefaultDisplayPropertySet</Name>
                        <ReferencedProperties>
                            <Name>Event Title</Name>
                            <Name>Venue</Name>
                            <Name>Type</Name>
                            <Name>Start Time</Name>
                            <Name>End Time</Name>
                            <Name>Duration</Name>
                            <Name>Notes</Name>
                        </ReferencedProperties>
                    </PropertySet>
                </Members>
            </MemberSet>
            <AliasProperty>
                <Name>Event Title</Name>
                <ReferencedMemberName>EventTitle</ReferencedMemberName>
            </AliasProperty>
            <AliasProperty>
                <Name>Start Time</Name>
                <ReferencedMemberName>StartTime</ReferencedMemberName>
            </AliasProperty>
            <AliasProperty>
                <Name>End Time</Name>
                <ReferencedMemberName>EndTime</ReferencedMemberName>
            </AliasProperty>
            <ScriptProperty>
                <Name>Duration</Name>
                <GetScriptBlock>($this.EndTime - $this.StartTime)</GetScriptBlock>
            </ScriptProperty>
        </Members>
    </Type>
</Types>

実際の運用では、このデータをClixmlとして出力し、後ほど読み込めるようにします。PowerShellではこれを可能にする便利なCmdletが用意されています。

$schedule | Export-Clixml schedule.xml

これを読み込むのも容易です。

$schedule = Import-Clixml schedule.xml

これにより、出力されたXMLは取得したデータのスナップショットになるので、オフラインでのアクセスが可能になります。

このデータをもとに、CSV (スケジュール情報等を集計するのに使用) する他、iCalendarなどのフォーマットを出力し、Google Calendarに入力することが可能になります。

このデータ構造により、Where-Objectを使用することにより、必要な情報を取り出すことができるようになります。例えば、「6C」で行われるイベントを知りたい場合は以下のようになります。

$schedule | ?{ $_.Venue -eq "6C" }

これに加え、以下のようなスクリプトを組み合わせました。

function Get-ScNextEvent {
    param(
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $Schedule,
        [parameter(Mandatory = $false)]
        [uint] $Hour = 1
    )
    $result = $Schedule | Where-Object { ($_.StartTime -ge [DateTime]::Now) -and ($_.EndTime -le [DateTime]::Now.AddHours($Hour))}

    return ($result | Sort-Object StartTime)
}

function Get-ScCurrentEvent {
    param(
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $Schedule
    )
    $result = $Schedule | Where-Object { ($_.StartTime -ge [DateTime]::Now) -and ($_.EndTime -le [DateTime]::Now)}

    return ($result | Sort-Object StartTime)
}

これにより、次のイベントや、現在進行中のイベントに関する情報を検索することができます。

他の分野でのPowerShell Coreの使用

PowerShell Core`はスケジュール管理以外の分野でも使用しました。他の部分で使用したのは定形文書を生成する管理システムです。ほとんどの定型文書はLaTeXソースとして生成されているため、コマンドラインより、パーソナライズされた情報を読み込む形式になっています。PowerShellはCSVをデータ構造に変換する仕組みを持っているため、必要な引数はCSVでまとめました。

Windowsでのコマンドラインの文字列の扱いには難があるため、これをWindowsで実行する場合にはWindows Subsystem for Linux (WSL)を使用する必要がありましたが、PowerShell CoreはLinuxでも動作するため、同じモジュールやスクリプトをそのまま使用することが可能でした。

これにより、20通を30秒程度で生成できます。

まとめ

PowerShell Coreにより、クロスプラットフォームな、一貫した環境をリレーション部内で使用する必要のある一部の情報の処理で使用しました。

今後はこのシステムを拡張し、スケジュールの競合探知、負荷確認や人員管理などにも適用できるようにしていく予定です。

ReSharperが指摘してくれるコードスタイルのあれこれ

ReSharper(と同様のエンジンを内蔵しているRider)が指摘してくれるものにはいろいろな種類のものがあるのですがそのいくつかに関する考察。

Collection Initializerへの置き換え

次のようなコードがあったとします。

var list = new List();
list.Add(new Something() {unit = "One", value = 5});
list.Add(new Something() {unit = "Two", value = 6});

ReShaperは次のような置き換えを提案してきます。

var list = new List
{
    new Something() {unit = "One", value = 5},
    new Something() {unit = "Two", value = 6}
};

これは確かに冗長になりがちなのでシンプルな書き換えにするのにはいい方法。

Foreach文のLINQ式への置き換え

時々出てくるパターンとしてリストの中を辿りつつ内容に対して処理を行うパターン。foreachで辿るようなパターン、例えば次のようなパターンがあったとします。

foreach (var item in list)
{
     if (item.unit == "Two")
        result += item.value;
}

これは以下のように置き換えることを提案してきます。

var result = list.Where(item => item.unit == "Two").Sum(item => item.value);

処理が複雑になると可読性が失われたり、デバッグが複雑になったりすることがあるのでその点は注意です。

メソッドのstatic化

次のようなクラスがあったとします。

internal class Hoge
{
    private int _value;

    public int Foo(int v)
    {
        _value += v;
        return _value;
    }

    public int Bar(int v)
    {
        v -= 1;
        return v;
    }
}

ReSharperはこのうち、public int Bar(int v)をstatic化するように提案してきます。

internal class Hoge
{
    private int _value;

    public int Foo(int v)
    {
        _value += v;
        return _value;
    }

    public static int Bar(int v)
    {
        v -= 1;
        return v;
    }
}

これはインスタンス化を行うとその分、要求されるメモリが増えるので、必要がないものにおいてはVM内で共有したほうがいい、ということですね。

ただ、staticメソッドのインスタンス化はそのメンバを呼び出したタイミングで行われたり、ガーベジコレクションの挙動が普通とは異なってくるはず(というか、スコープから外れない場合は残存し続けるので、長期的に残り続ける挙動を示すはずです)のでその部分では注意が必要です。

JetBrains All Products Pack

最近JetBrainsのAll Products Packを購入したのでいろいろと。

まずはそれぞれのツールに関していろいろと書いてみる。

Rider

まずいちばん使っているのがこのRider。C#のIDEです。現段階で使っているのは.NET Core 2.0の機能を使いたいので、EAP版です。

ちなみにRiderはUnityと組み合わせ使うことも可能です。Linux版のUnity Editorと組み合わせて使用する場合に便利です。

機能面ではVisual StudioResharperを組み合わせたものに近いです。(Windows Formなどのビジュアルエディターはありませんが……。)

尚、Reshaperに関しても上位版であるResharper Ultimateが、All Products Packに含まれています。

プライベートではマルチプラットフォーム開発なので、あまり使う機会はなかったりしますが。(ただし、使う場合Visual Studio Communityでもちゃんと動作します。)

DataGrip

次によく使うのがDataGripです。DataGripはデータベース用のIDEです。

いろいろなデータベースをサポートしていて、SQL文を発行したり、内容を調べたりすることができます。

Gogland

GoglandはGoogleにより開発されている、Go用のIDEです。現在EAPで無料で使用できます。正式版では名前が変わるかも知れないようです。

IntelliJ IDEA Ultimate

IntelliJ IDEA UltimateJavaのIDEです。(正確にはJVMな言語は多くが対応しています。KotlinもJetBrainsが開発していますし、もちろん対応しています。)

尚、IntelliJ IDEA Ultimateにはオープンソース版のIntelliJ IDEA Communityも存在します。これ一本でJavaのアプリ、Androidのアプリなどを開発したりできます。プラグインを通してGoやPythonなどの他の言語も開発することができるようですが、挙動など言語特化のIDEがより言語に特化した動作をするようになっているようです。

個人的にはJavaはあまり使わないので、XSLTの編集などに使ったりする場合が多いです。

PyCharm Professional

PyCharm ProfessionalPythonのIDEです。。こちらもPyCharm Communityというオープンソース版の他、PyCharm Eduという教育目的に特化した特別版も存在します。

PhpStorm

PhpStormPHPのIDEです。あまり新規にPHPは少なくとも自分で書くことは少ないのですが、レガシーなコードはあるのでその保守に使用しています。

CLion

CLionはC/C++のIDEです。Linux上で時々C/C++を扱ったりするので、その場合に活用しているツールです。

RubyMine

RubyMineRubyのIDE。Rubyのコードは現在PHPよりも少ないのであまり出番はないですが……。

MPS

イマイチ使い方がよくわかっていないのが、このMPS。IDEというよりはメタ言語みたいなのですが……。

JetBrainsのAll Products Packは開発機材として個人的に買う場合は、そこまでは高くはないのですが(ビジネスとして買う場合はそれなりに高くなりますが……)それなりの額にはなりますが、今のところはその値打ちはあるように感じています。特に複数の言語を使う必要がある場合同じ使用感で複数の言語を扱えますので。

実行時間が短いWindowsサービスの問題について

One-shotなサービスで持続時間が短いとWindowsのサービスはエラーが起きたものとして処理する場合があるらしい。

エレガントではない方法としてはThread.Sleep()で少し遅延させてやるようなのがいいかも知れない。(C#の場合。)