Scala, F#, Ruby によるメール送信 - メールで Evernote にノート登録

メールを使った Evernote へのノート登録を Scala, F#, Ruby で試してみました。

Evernote は、ノート登録用のメールアドレス(アカウント情報に記載あり)に以下のような Subject(件名)でメール送信するだけでノート登録ができます。(メールの本文がノートの内容となる)

Subjectの命名規則
ノート名 @ノートブック #タグ1 #タグ2 ・・・

ただし、# を含んだタグ(例 F#)の指定方法はわかりません。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20101101/


なお、各プログラムは以下の 4つのパラメータを実行時引数として与え、標準入力の内容をメール本文にします。

  1. SMTPサーバー名
  2. 送信者(From)メールアドレス
  3. 宛先(To)メールアドレス
  4. 件名(Subject)

また、件名と標準入力の文字コードShift_JIS とし、メールは UTF-8 形式で送信する事にします。(件名も UTF-8

ちなみに、Evernote へのノート登録は JIS(ISO-2022-JP)でも特に問題無いようでした。

Scala でメール送信

JavaMail を直接使うのは面倒なので、今回は Commons Email を使いました。

charset に UTF-8 を設定すれば UTF-8 で送信できます。

send_mail.scala
import scala.io.Source
import org.apache.commons.mail.SimpleEmail

new SimpleEmail {
    charset = "UTF-8"
    hostName = args(0)
    setFrom(args(1))
    addTo(args(2))
    subject = args(3)
    //標準入力を文字列化して本文に設定
    setMsg(Source.stdin.mkString)
}.send
実行例(test ノートブックにタグ "scala" "java" "開発" を付けてノート登録)
> set CLASSPATH=commons-email-1.2.jar;mail.jar;%CLASSPATH%
> scala send_mail.scala localhost xxxx@xxx.com xxxx@m.evernote.com "テストノート by Scala @test #scala #java #開発" < mail.txt

F# でメール送信

.NET Framework の System.Net.Mail.SmtpClient を使いました。
デフォルト UTF-8 でメール送信するので文字コードを指定する必要はありません。

send_mail.fs
open System
open System.Net.Mail

[<EntryPoint>]
let main(args: string[]) =
    let smtp = new SmtpClient(args.[0])
    //標準入力を文字列化
    let body = Console.In.ReadToEnd()
    //メール送信
    smtp.Send(args.[1], args.[2], args.[3], body)
    0
実行例(test ノートブックにタグ "fsharp" ".NET" "開発" を付けてノート登録)
  • F# 2.0.0
> fsc send_mail.fs
> send_mail.exe localhost xxxx@xxx.com xxxx@m.evernote.com "テストノート by Fsharp @test #fsharp #.NET #開発" < mail.txt

Ruby でメール送信

Net::SMTP だと文字コードまわりが面倒そうだったので、ActionMailer 3.0 を単体で使ってみました。

  • ActionMailer 3.0

ActionMailer::Base のサブクラスを定義するのが一般的な使い方(Rails の場合)だと思いますが、今回は ActionMailer::Base を直接使ってます。

なお、enable_starttls_auto は環境に合わせて設定すればよいと思います。

また、デフォルト UTF-8 でメール送信するのでメール送信時の文字コードを指定する必要はありませんが、Subject やメール本文に使用する文字列は事前に Shift_JIS から UTF-8文字コード変換を行っておく必要があります。

send_mail.rb(Ruby1.9用)
require "rubygems"
require "action_mailer"

ActionMailer::Base.smtp_settings = {
    #SMTPサーバー設定
    :address => ARGV[0],
    :enable_starttls_auto => false
}

subject = ARGV[3].encode("UTF-8", "Shift_JIS")
#標準入力を文字列化
body = $stdin.readlines.join.encode("UTF-8", "Shift_JIS")

ActionMailer::Base.mail(:from => ARGV[1], :to => ARGV[2], :subject => subject, :body => body).deliver

Windows 環境だと個人的に JRuby の方が使い勝手が良い気がしているので、JRuby で実行しています。(encode を使用できるようにするため --1.9 オプションを指定する必要があります)

実行例(test ノートブックにタグ "ruby" "java" "開発" を付けてノート登録)
> jruby --1.9 send_mail.rb localhost xxxx@xxx.com xxxx@m.evernote.com "テストノート by JRuby @test #ruby #java #開発" < mail.txt

Visual Studio を使わずに F# で Silverlight 4 のサンプル作成 - fsc.exe を用いた手動作成

Visual F# 等の助けを借りず、Silverlight 4 の簡単なサンプルを fsc.exe を使って手動作成してみました。

以前 id:fits:20080310 に Silverlight 2 のサンプルを IronPython で作った方法と似てますが、今回は AppManifest.xaml も自前で用意するので Chiron.exe も必要ありません。(ただし、zip 圧縮の手段は必要)

とりあえず、以下と Silverlight 4 の SDK をインストールしておきます。(今回の用途では Silverlight のランタイムだけあればよくて SDK は要らないかも)

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20101017/

アプリケーション作成

まず、メインアプリケーションの UI を XAML で記述します。

app/MainPage.xaml
<UserControl 
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="System.Windows.Controls.UserControl"
    x:Name="Page">

    <StackPanel Background="White">
        <TextBlock Name="label1" Text="サンプルリスト" />
        <ListBox Name="listBox1" Margin="10 10 0 0" HorizontalAlignment="Left" Width="300" Height="200" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path=Id}" />
                        <TextBlock Text="{Binding Path=Title}" Margin="30 0 0 0" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</UserControl>

画面イメージは以下の通り。

次に、上記で作成した XAML(MainPage.xaml)を読み込んで表示するメインアプリケーションを F# で実装します。(Visual StudioSilverlight を作成する場合の App.xaml・App.xaml.cs 等に相当)

処理内容は以下の通り。

  • do バインディング内で Startup イベントのイベントハンドラ(ラムダ式を使用)を追加
  • do バインディング内で this を使用するため "as this" を定義
  • ":?> ListBox" で ListBox 型へダウンキャスト(C# の "as 型" みたいなもの)
  • ListBox の表示データに F# のレコード型を使用(定義は type Data の箇所、インスタンス作成は {Id = ・・・} の箇所)
src/ListSampleApp.fs
namespace ListSample

open System
open System.Windows
open System.Windows.Controls

//表示用データ定義(レコード型)
type Data = {
    Id: string
    Title: string
}

//アプリケーションクラスの定義
type ListSampleApp() as this =
    inherit Application()

    //初期化処理
    do
        let xamlUri = new Uri("MainPage.xaml", UriKind.Relative)

        //Startup イベント時の処理を追加
        this.Startup.AddHandler(fun _ _ -> 
            let control = new UserControl()
            //XAML 読み込み
            Application.LoadComponent(control, xamlUri)
            //コントロールの表示
            this.RootVisual <- control

            //ListBox 取得
            let listBox = control.FindName("listBox1") :?> ListBox
            //ListBox への表示用データ設定(Data のインスタンスを作成)
            listBox.ItemsSource <- [
                {Id = "A001"; Title = "XAMLファイルを出力する方法"}
                {Id = "A002"; Title = "WPF レイアウト"}
                {Id = "B001"; Title = "F# ラムダ式の記法"}
            ]
        )

なお、ListBox の取得時にダウンキャストを使ってますが、パターンマッチを使った方が望ましいかもしれません。

ビルド

fsc.exe で src\ListSampleApp.fs をビルドして app ディレクトリに ListSample.dll ファイルを作成します。
fsc.exe には以下のようなオプションを指定する必要があります。

  • "--noframework" で通常の .NET Framework が参照されるのを防止
  • F# の Silverlight 用アセンブリ FSharp.Core.dll を参照指定
  • 実装内容に応じて Silverlight 用の各種アセンブリを参照指定

なお。fsc.exe は .NET 2.0 用のものを使う点に注意。(例 C:\FSharp-2.0.0.0\bin\fsc.exe を使う)

ビルドの内容をバッチファイル化すると以下のような内容になります。(アセンブリの位置は環境に応じて変更が必要)

ビルド用バッチファイル例 build.bat
set FSHARP_SL_LIB=C:\FSharp-2.0.0.0\Silverlight\2.0\bin
set SL_LIB=C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0

fsc.exe -a --noframework --out:app\ListSample.dll -r:"%FSHARP_SL_LIB%\FSharp.Core.dll" -r:"%SL_LIB%\System.dll" -r:"%SL_LIB%\System.Windows.dll" src\ListSampleApp.fs

copy %FSHARP_SL_LIB%\FSharp.Core.dll app\

なお、FSharp.Core.dll は後で XAP ファイルに含める事になるので、app ディレクトリにコピーしておきます。

C:\FSharp-2.0.0.0\bin を環境変数 PATH に設定しておき、build.bat を実行します。

build.bat 実行例
> build.bat

AppManifest.xaml 作成

AppManifest.xaml を作成します。(Visual Studio や Chiron.exe とかだと自動で作ってくれる)
ListSample.dll・FSharp.Core.dll 用の AssemblyPart の定義、EntryPointAssembly にアプリケーションのアセンブリ・EntryPointType にアプリケーションクラスをそれぞれ設定します。

app/AppManifest.xaml
<Deployment 
    xmlns="http://schemas.microsoft.com/client/2007/deployment" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    EntryPointAssembly="ListSample" 
    EntryPointType="ListSample.ListSampleApp" 
    RuntimeVersion="4.0.50826.0">

    <Deployment.Parts>
        <AssemblyPart x:Name="ListSample" Source="ListSample.dll" />
        <AssemblyPart Source="FSharp.Core.dll" />
    </Deployment.Parts>
</Deployment>

XAP ファイル作成

これまでに作成したファイル(app ディレクトリ内の配置されているはず)を zip 圧縮して XAP ファイル(ListSample.xap)を作成します。(app ディレクトリを含めない点に注意)

今回は署名とかしないので、普通に zip 圧縮して必要に応じて拡張子を変更すれば OK です。

app ディレクトリ内の構成例
  • app
    • AppManifest.xaml
    • FSharp.Core.dll
    • ListSample.dll
    • MainPage.xaml
ListSample.xap ファイル内の構成例
  • AppManifest.xaml
  • FSharp.Core.dll
  • ListSample.dll
  • MainPage.xaml

ちなみに、IronRuby 1.1 等に含まれている Chiron.exe(silverlight\bin\Chiron.exe)を使って XAP ファイルを作成するには以下のように /x オプションを使います。

Chiron.exe での作成例
> Chiron.exe /d:app /x:ListSample.xap

動作確認

以下のような Silverlight 実行用の HTML を用意して、Web ブラウザで実行します。

index.html
<!DOCTYPE html>
<html>
<head>
<title>ListSample</title>
<link rel="stylesheet" href="default.css" type="text/css" />
<script type="text/javascript" src="Silverlight.js"></script>
</head>
<body>
    <div id="silverlightControlHost">
        <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
            <param name="source" value="ListSample.xap"/>
            <param name="background" value="white" />
            <param name="minRuntimeVersion" value="4.0.50826.0" />
        </object>
    </div>
</body>
</html>

Sinatra風にASP.NET(F# 編)

前回 id:fits:20100920 作成したサンプルを F# で実装しなおしてみた。
ASP.NET で F# を使う設定に関しては id:fits:20100906 参照)

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20100923/

Get, Post メソッドの定義

C# では拡張メソッドを使って実装していた Get, Post メソッドを F# の型拡張で。
そして、各種インターフェースの実装を F# のオブジェクト式で実装。

CustomRouting.fs
namespace Fits.Sample.Web.Routing

open System
open System.Web
open System.Web.Routing

module CustomRouting = 
    //HttpApplication に型拡張を適用
    type HttpApplication with
        member this.Get(pattern: string, proc: Func<RequestContext, string>) =
            this.Action("GET", pattern, proc)

        member this.Post(pattern: string, proc: Func<RequestContext, string>) =
            this.Action("POST", pattern, proc)

        member this.Action(methodType: string, pattern: string, proc: Func<RequestContext, string>) =
            let r = new Route(pattern, {
                //IRouteHandler インターフェースの実装
                new IRouteHandler with
                    member this.GetHttpHandler(rctx: RequestContext) = {
                        //IHttpHandler インターフェースの実装
                        new IHttpHandler with
                            member this.IsReusable = true
                            member this.ProcessRequest(hctx: HttpContext) = 
                                let res = proc.Invoke(rctx)
                                hctx.Response.Write(res)
                    }
            })

            //httpMethod の制約を設定
            let rv = RouteValueDictionary()
            rv.Add("httpMethod", new HttpMethodConstraint(methodType))
            r.Constraints <- rv

            //ルーティングの追加
            RouteTable.Routes.Add(r)

上記ファイルをビルドして、bin ディレクトリに DLL を配置しておきます。

ビルド
>fsc --target:library --out:bin\Fits.Sample.Web.dll CustomRouting.fs

なお、F# のホームディレクトリにある v4.0\bin 内の fsc.exe でビルドしなければならない点に注意。(ホームディレクトリ直下の bin\fsc.exe を使用すると System.Web.Routing が参照できない)

Global.asax の定義

前回と同等の Global.asax を F# で実装します。今回は実行時に Global.asax.fs がビルドされるように Codebehind 属性の代わりに CodeFile 属性を使いました。

Global.asax
<%@ Application CodeFile="Global.asax.fs" Inherits="Fits.Sample.Web.Global" Language="F#" %>

基本的に C# 版と同等の実装ですが、型拡張を行うために CustomRouting を「名前空間.モジュール名」で open している点と RouteData.Values から値を取得する際に ".[キー名]" としている点に注意。

Global.asax.fs
namespace Fits.Sample.Web

open System
open System.Web
//型拡張の適用(名前空間.モジュール名)
open Fits.Sample.Web.Routing.CustomRouting

type Global() = 
    inherit HttpApplication()

    member this.Application_Start(sender: Object, e: EventArgs) = 
        // test/:index への POST に対する定義
        //(例) "test/1" への POST で WebForm1.aspx にリダイレクト
        this.Post("test/{index}", fun ctx -> 
            ctx.HttpContext.Response.Redirect("/WebForm1.aspx")
            null
        )

        // /:name/:index への GET に対する定義
        //(例) "test/1" への GET で "hello test - 1" という文字列を表示
        this.Get("{name}/{index}", fun ctx -> 
            let pm = ctx.RouteData.Values
            sprintf "hello %O - %O" pm.["name"] pm.["index"]
        )

動作確認

WebDev.WebServer40.exe を使って動作確認できます。

ASP.NET 開発サーバー実行バッチ例(start_webserver.bat)
WebDev.WebServer40.exe /port:8080 /path:%~d0%~p0

F# で ASP.NET

F# で ASP.NET を実装してみました。
Visual Web Developer 2010 Express では、F# 用プロジェクトは作成してくれないみたいだったので(Visual F# が要ると思う)、自前で Web.config ファイルを用意し、Visual Web Developer を使わずに実装してみる事にします。

とりあえず、以下をインストールしておきます。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20100906/

設定ファイル Web.confg の用意

Web.config ファイルに以下のような F# コンパイラの設定を行えば、ASP.NET で F# が使用できるようになります。
なお、FSharpAspNetCodeProvider は F# PowerPack に含まれるのでインストールしておく必要があります。

Web.config
<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.codedom>
    <compilers>
      <compiler language="F#;f#;fs;fsharp"
         extension=".fs"
         type="Microsoft.FSharp.Compiler.CodeDom.FSharpAspNetCodeProvider,
               FSharp.Compiler.CodeDom,
               Version=2.0.0.0,
               Culture=neutral,
               PublicKeyToken=a19089b1c74d0809"/>
    </compilers>
  </system.codedom>
</configuration>

トップページの実装

それでは、ASP.NET のページを作成します。
Language で F# を指定する以外は一般的な ASP.NET のページを作成する事になります。

Default.aspx
<%@ Page Language="F#" AutoEventWireup="true" CodeFile="Default.aspx.fs" Inherits="Fits.Sample.DefaultPage"%>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>F# Sample</title>
</head>
<body>
  <form runat="server">
    <div>
      <asp:TextBox runat="server" id="InfoText" />
      <asp:Button runat="server" id="InfoButton" text="Button" onClick="InfoButton_Click" />
    </div>
    <div>
      <asp:Label runat="server" id="InfoLabel" />
    </div>
  </form>
</body>
</html>

次に、F# による処理の実装を行います。
自分で配置したコントロール類は、"[] val mutable" で定義します。
val キーワードはフィールドを初期化無しで宣言するためのもので、DefaultValue 属性でゼロ初期化(今回のケースでは null で初期化)を指定しています。

ちなみに、val キーワードを使って宣言されたフィールドは「明示的なフィールド」と呼ばれます。

Default.aspx.fs
namespace Fits.Sample

open System
open System.Web
open System.Web.UI
open System.Web.UI.WebControls

type DefaultPage() =
    inherit Page()

    [<DefaultValue>] val mutable InfoText : TextBox
    [<DefaultValue>] val mutable InfoButton : Button
    [<DefaultValue>] val mutable InfoLabel : Label

    //ページロード時の処理
    member this.Page_Load(sender : obj, e : EventArgs) =
        this.InfoLabel.Text <- "hello"

    //ボタンクリック時の処理
    member this.InfoButton_Click(sender : obj, e : EventArgs) = 
        this.InfoLabel.Text <- "入力: " + this.InfoText.Text

動作確認

ASP.NET 開発サーバー(通常は C:\Program Files\Common Files\microsoft shared\DevServer\10.0 にインストールされているはず)を使って動作確認を行います。

以下のようにコマンド実行すると、path で指定したディレクトリ内のファイルを ASP.NET で実行します。(path は絶対パスを指定する点に注意)

ASP.NET 開発サーバー実行
>WebDev.WebServer40.exe /port:8080 /path:d:\try_samples\blog\20100906\asp.net_fsharp

上記コマンドの実行後、http://localhost:8080/ に Web ブラウザで接続し、Button ボタンをクリックすると動作している事が確認できるはずです。