namespace Personal.FSharp.TypeProviders
open System
open System.IO
open System.Reflection
open System.Runtime.InteropServices
open System.Text
open Microsoft.FSharp.Core.CompilerServices
open Samples.FSharp.ProvidedTypes
module Win32 =
[<DllImport("kernel32", CharSet = CharSet.Unicode)>]
extern int GetPrivateProfileSectionNamesW(
byte[] lpszReturnBuffer,
int nSize,
string lpFileName)
[<DllImport("kernel32", CharSet = CharSet.Unicode)>]
extern int GetPrivateProfileSectionW(
string lpAppName,
byte[] lpReturnedString,
int nSize,
string lpFileName)
[<DllImport("kernel32", CharSet = CharSet.Unicode)>]
extern int GetPrivateProfileStringW(
string lpAppName,
string lpKeyName,
string lpDefault,
StringBuilder lpReturnedString,
int nSize,
string lpFileName)
module Util =
let private SplitNullTerminatedBuffer(buf : byte[], count : int) =
// wchar用に文字数の倍の数分だけバッファを読む
let namesBuf = Encoding.Unicode.GetString(buf, 0, count*2)
namesBuf.Split([| (char)0 |], StringSplitOptions.RemoveEmptyEntries)
/// <summary>INIファイル内にあるすべてのセクション名を取得します。</summary>
/// <param name="fileName">INIファイルの名前。</param>
let GetAllSectionNames(fileName) =
let buf = Array.zeroCreate 10240
let numBuffer = Win32.GetPrivateProfileSectionNamesW(buf, buf.Length, fileName)
SplitNullTerminatedBuffer(buf, numBuffer)
/// <summary>INIファイル内の特定セクションにあるすべてのキーを取得します。</summary>
/// <param name="fileName">INIファイルの名前。</param>
/// <param name="sectionName">INIファイル内にあるセクション名。</param>
let GetAllKeysInSection(fileName, sectionName) =
let buf = Array.zeroCreate 10240
let numBuffer = Win32.GetPrivateProfileSectionW(sectionName, buf, buf.Length, fileName)
let values = SplitNullTerminatedBuffer(buf, numBuffer)
seq { for i in 0 .. values.Length - 1 do
let kvp = values.[i].Split([|'='|], 2)
yield kvp.[0] }
/// <summary>INIファイル内の特定セクションにあるキーの値を取得します。</summary>
/// <param name="fileName">INIファイルの名前。</param>
/// <param name="sectionName">INIファイル内にあるセクション名。</param>
/// <param name="keyName">取得したい値を持つキーの名前。</param>
/// <param name="defaultValue">デフォルト値。</param>
let GetSectionValue(fileName, sectionName, keyName, defaultValue) =
let buf = new StringBuilder(10240)
Win32.GetPrivateProfileStringW(sectionName, keyName, defaultValue, buf, buf.Capacity, fileName) |> ignore
buf.ToString()
/// <summary>INIファイル内にあるセクションを表します。</summary>
/// <param name="fileName">INIファイルの名前。</param>
/// <param name="sectionName">INIファイル内にあるセクション名。</param>
type IniSection(fileName, sectionName) =
let keys = seq { for key in Util.GetAllKeysInSection(fileName, sectionName) do yield key }
/// 現在のセクションの名前。
member __.Name = sectionName
/// 現在のセクション内にあるすべてのキー。
member __.Keys = keys |> Seq.toArray
/// <summary>
/// 現在のセクション内にある特定のキーの値を取得します。
/// <para>キーが見つからない場合、<paramref name="defaultValue">の値が返されます。</para>
/// </summary>
/// <param name="key">取得したい値を持つキーの名前。</param>
/// <param name="defaultValue">デフォルト値。</param>
member __.GetValue(key, defaultValue) = Util.GetSectionValue(fileName, sectionName, key, defaultValue)
/// <summary>INIファイルを表します。</summary>
/// <param name="fileName">INIファイルの名前。</param>
type IniFile(fileName) =
let sections = seq { for name in Util.GetAllSectionNames(fileName) do yield name } |> Seq.cache
/// INIファイルの名前。
member __.FileName = fileName
/// INIファイル内にあるすべてのセクション名。
member __.Sections = sections |> Seq.toArray
/// <summary>INIファイル内にある特定のセクションを取得します。</summary>
/// <param name="name">INIファイル内にあるセクション名。</param>
member __.GetSection(name) = IniSection(fileName, name)
/// <summary>INIファイル用のType Providerを実装する型。</summary>
/// <param name="config"><see ref="Microsoft.FSharp.Core.CompilerServices.TypeProviderConfig"/>による構成をサポートします。</param>
[<TypeProvider>]
type IniFileTypeProvider(config : TypeProviderConfig) as this =
inherit TypeProviderForNamespaces()
let asm = Assembly.GetExecutingAssembly()
let ns = "Personal.FSharp.TypeProviders"
// INIファイル用Type Providerの起点となる型の定義。
// このインスタンスに様々なメンバや型定義を追加していきます。
let iniTy = ProvidedTypeDefinition(asm, ns, "Ini", Some(typeof<obj>))
// Type Provider使用時にstatic引数で指定された値。
// 今回はINIファイルの名前を指定できるようにするので、
// string型のProvidedStaticParameterを1つ用意します。
let filename = ProvidedStaticParameter("filename", typeof<string>)
// static引数の実体。
let applyFunc = fun (tyName:string) (parameters:obj[]) ->
match parameters with
| [| :? string as filename |] ->
let ty = ProvidedTypeDefinition(asm, ns, tyName, Some(typeof<IniFile>))
let resolvedFilename = Path.GetFullPath(Path.Combine(config.ResolutionFolder, filename))
let iniFile = new IniFile(resolvedFilename)
// それぞれのセクションと同じ名前のプロパティを追加
iniFile.Sections
|> Seq.iter (fun section ->
let sectionTy = ProvidedTypeDefinition(section, Some(typeof<IniSection>))
// それぞれのキーと同じ名前のプロパティを追加
iniFile.GetSection(section).Keys
|> Seq.iter (fun key ->
let keyProp = ProvidedProperty(key, typeof<string>,
GetterCode = fun args -> <@@ (%%args.[0] : IniSection).GetValue(key, null) @@>)
sectionTy.AddMember keyProp)
let prop = ProvidedProperty(section, sectionTy,
GetterCode = fun args -> <@@ (%%args.[0] : IniFile).GetSection(section) @@>)
ty.AddMember prop
ty.AddMember sectionTy)
let ctor0 = ProvidedConstructor([],
InvokeCode = fun [] -> <@@ IniFile(resolvedFilename) @@>)
ty.AddMember ctor0
ty
| _ -> failwith "Invalid parameter"
do iniTy.DefineStaticParameters([filename], applyFunc)
do this.AddNamespace(ns, [ iniTy ])
[<assembly:TypeProviderAssembly>]
do ()