Commit 98d8bb36 authored by Yuanle Song's avatar Yuanle Song
Browse files

add config parser support.

now target can be specified in config file.
renamed some function to use camelCase.
parent fe68cf51
Loading
Loading
Loading
Loading

ConfigParser.fs

0 → 100644
+39 −0
Original line number Diff line number Diff line
module Mbackup.ConfigParser

open System
open System.IO
open System.Text.RegularExpressions

open Mbackup.Lib

type WellsConfig(fn: string) =
  let mutable keyValuePairs: Map<string, string> = Map.empty
  do
    let dropEmptyLinesAndComments lines = Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals(""))) lines
    let dropQuotesMaybe (value: string) = if value.StartsWith("\"") || value.StartsWith("'") then value.Substring(1, value.Length - 2) else value
    let toKeyValue = Seq.map (fun (line: string) ->
                                let result: string[] = line.Split('=', 2)
                                (result.[0].Trim(), dropQuotesMaybe (result.[1].Trim())))
    let result = File.ReadAllLines(fn)  // file IO can throw Exception
                 |> dropEmptyLinesAndComments
                 |> toKeyValue
                 |> Seq.fold (fun (m: Map<string, string>) (k, v) -> m.Add(k, v)) keyValuePairs
    keyValuePairs <- result
  member this.ConfigFile = fn
  member this.GetStr key =
    let getEnv (varName: string) = Environment.GetEnvironmentVariable varName
    let configKeyToEnvVar (key: string) =
      key.ToUpper().Replace(".", "_").Replace("-", "_")
    match getEnv (configKeyToEnvVar key) with
      | null | "" -> keyValuePairs.TryFind key
      | envValue -> Some envValue
  member this.GetBool key =
    Option.map (fun value -> Regex.IsMatch(value, "^(yes|true|enable|1)$", RegexOptions.IgnoreCase)) (this.GetStr key)
  member this.GetFloat key =
    let value = keyValuePairs.TryGetValue key
    let parseFloat s = try Some (float s) with | _ -> None
    Option.map parseFloat (this.GetStr key)
  member this.GetInt key =
    let value = keyValuePairs.TryGetValue key
    let parseInt s = try Some (int s) with | _ -> None
    Option.map parseInt (this.GetStr key)
+5 −8
Original line number Diff line number Diff line
@@ -39,9 +39,9 @@ type Logger() =
// append string s to list if pred is true
let appendWhen (pred: bool) (lst: string list) (s: string) = if pred then List.append lst [s] else lst

let GetEnv (varName: string) = Environment.GetEnvironmentVariable varName
let getEnv (varName: string) = Environment.GetEnvironmentVariable varName

let GetEnvDefault (varName: string) (defaultValue: string) =
let getEnvDefault (varName: string) (defaultValue: string) =
  let value = Environment.GetEnvironmentVariable varName
  match value with
    | null -> defaultValue
@@ -51,7 +51,7 @@ let GetEnvDefault (varName: string) (defaultValue: string) =
// Convert windows path to Mingw64 path.
// Supported windows path: C:\foo, C:/foo, /c/foo
// MingwPath format: /cygdrive/c/foo
let ToMingwPath (windowsPath: string) =
let toMingwPath (windowsPath: string) =
  let pattern = Regex("^/([c-zC-Z])/", RegexOptions.None)
  let result =
    if pattern.IsMatch(windowsPath) then
@@ -65,8 +65,5 @@ let ToMingwPath (windowsPath: string) =
        windowsPath
  result

let EnsureDir (path: string) = if path.EndsWith "/" then path else path + "/"
let EnsureWinDir (path: string) = if path.EndsWith "\\" then path else path + "\\"

let parseMbackupConfig fn =
  ()
let ensureDir (path: string) = if path.EndsWith "/" then path else path + "/"
let ensureWinDir (path: string) = if path.EndsWith "\\" then path else path + "\\"
+26 −25
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ open System.Diagnostics.CodeAnalysis
open Argu

open Mbackup.Lib
open Mbackup.ConfigParser

let ExitBadParam = 1
let ExitTimeout = 2
@@ -43,28 +44,27 @@ with
            | Node_Name _ -> "local node's name, used in remote logging"
            | Ssh_Key _ -> "ssh private key, used when backup to remote ssh node"

let appDataRoamingDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) |> ToMingwPath |> EnsureDir
let programDataDirWin = GetEnv "PROGRAMDATA" |> EnsureWinDir
let programDataDir = ToMingwPath programDataDirWin
let appDataLocalDirWin =  Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) |> EnsureWinDir
let appDataLocalDir =  appDataLocalDirWin |> ToMingwPath
let appDataRoamingDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) |> toMingwPath |> ensureDir
let programDataDirWin = getEnv "PROGRAMDATA" |> ensureWinDir
let programDataDir = toMingwPath programDataDirWin
let appDataLocalDirWin =  Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) |> ensureWinDir
let appDataLocalDir =  appDataLocalDirWin |> toMingwPath

let mbackupInstallDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> EnsureDir |> fun s -> s + "mbackup"
let mbackupInstallDir = mbackupInstallDirWin |> ToMingwPath
let mbackupInstallDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> ensureDir |> fun s -> s + "mbackup"
let mbackupInstallDir = mbackupInstallDirWin |> toMingwPath

let userHomeWin =
  GetEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile))
  |> EnsureWinDir
  getEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile))
  |> ensureWinDir

let userHome = userHomeWin |> ToMingwPath
let userHome = userHomeWin |> toMingwPath

let userConfigDirWin = programDataDirWin + "mbackup\\"
let userConfigDir = programDataDir + "mbackup/"
let runtimeDirWin = appDataLocalDirWin + "mbackup\\"
let runtimeDir = appDataLocalDir + "mbackup/"

let mbackupConfigFile = userConfigDirWin + "mbackup.ini"
let mbackupConfig: unit = parseMbackupConfig mbackupConfigFile
let mbackupConfigFile = userConfigDirWin + "mbackup.txt"

let isLocalTarget (target: string) = target.StartsWith "/"

@@ -75,9 +75,9 @@ let isLocalTarget (target: string) = target.StartsWith "/"
// etc
let expandUserFile (fn: string) =
  let fn =
    let documentsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) |> ToMingwPath |> EnsureDir
    let picturesDir = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) |> ToMingwPath |> EnsureDir
    let desktopDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) |> ToMingwPath |> EnsureDir
    let documentsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) |> toMingwPath |> ensureDir
    let picturesDir = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) |> toMingwPath |> ensureDir
    let desktopDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) |> toMingwPath |> ensureDir
    let fn = Regex.Replace(fn, "^My Documents/", documentsDir, RegexOptions.IgnoreCase)
    let fn = Regex.Replace(fn, "^Documents/", documentsDir, RegexOptions.IgnoreCase)
    let fn = Regex.Replace(fn, "^我的文档/", documentsDir)
@@ -103,14 +103,14 @@ let generateMbackupList (logger: Logger) =
    let mbackupList = runtimeDirWin + "mbackup.list"

    // local functions
    let dropEmptyLinesAndComments = Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals("")))
    let dropEmptyLinesAndComments lines = Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals(""))) lines
    let readMbackupListFile fn = File.ReadAllLines(fn) |> dropEmptyLinesAndComments

    try
      let defaultListLines = readMbackupListFile mbackupDefaultList |> Seq.map ToMingwPath
      let defaultListLines = readMbackupListFile mbackupDefaultList |> Seq.map toMingwPath
      let localListLinesMaybe =
        try
          let lines = readMbackupListFile mbackupLocalList |> Seq.map ToMingwPath
          let lines = readMbackupListFile mbackupLocalList |> Seq.map toMingwPath
          (true, lines)
        with
          | :? System.IO.FileNotFoundException ->
@@ -170,19 +170,20 @@ let main argv =

    // TODO remove usage of test dir.
    let mbackupInstallDirWinTest = "D:\\downloads\\apps\\mbackupTest\\"
    let mbackupInstallDirTest = mbackupInstallDirWinTest |> ToMingwPath |> EnsureDir
    let mbackupInstallDirTest = mbackupInstallDirWinTest |> toMingwPath |> ensureDir
    let sshExeFile = mbackupInstallDirTest + "rsync-w64/usr/bin/ssh.exe"
    let sshConfigFile = userHome + ".ssh/config"
    let sshPrivateKeyFile = results.GetResult(Ssh_Key, defaultValue = userHome + ".ssh/id_rsa") |> ToMingwPath
    let sshPrivateKeyFile = results.GetResult(Ssh_Key, defaultValue = userHome + ".ssh/id_rsa") |> toMingwPath
    let rsyncCmd = List.append rsyncCmd [sprintf "-e \"%s -F %s -i %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\"" sshExeFile sshConfigFile sshPrivateKeyFile]

    let backupTarget = results.GetResult (Target, defaultValue = Environment.GetEnvironmentVariable "TARGET")
    match backupTarget with
      | null ->
    let mbackupConfig = WellsConfig(mbackupConfigFile)
    let backupTargetMaybe = mbackupConfig.GetStr("target")
    match backupTargetMaybe with
      | None ->
          logger.Error "TARGET is not defined"
          ExitBadParam
      | _ ->
          let backupTarget = ToMingwPath backupTarget
      | Some backupTarget ->
          let backupTarget = toMingwPath backupTarget
          let rsyncCmd =
            if not (isLocalTarget backupTarget)
              then
+1 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@

  <ItemGroup>
    <Compile Include="Lib.fs" />
    <Compile Include="ConfigParser.fs" />
    <Compile Include="Program.fs" />
  </ItemGroup>

+25 −13
Original line number Diff line number Diff line
@@ -12,19 +12,19 @@ let TestDumb () =
    Assert.Pass()

[<Test>]
let TestToMingwPath () =
    Assert.That("/cygdrive/c/", Is.EqualTo(ToMingwPath "c:\\"))
    Assert.That("/cygdrive/c/", Is.EqualTo(ToMingwPath "C:\\"))
    Assert.That("/cygdrive/c/foo", Is.EqualTo(ToMingwPath "C:\\foo"))
    Assert.That("/cygdrive/d/foo", Is.EqualTo(ToMingwPath "D:\\foo"))
    Assert.That("/cygdrive/d/Foo", Is.EqualTo(ToMingwPath "D:\\Foo"))
    Assert.That("/cygdrive/c/foo/bar/", Is.EqualTo(ToMingwPath "C:\\foo\\bar\\"))
    Assert.That("/cygdrive/c/foo/bar/baz.txt", Is.EqualTo(ToMingwPath "C:\\foo\\bar\\baz.txt"))
    Assert.That("/cygdrive/c/foo", Is.EqualTo(ToMingwPath "C:/foo"))
    Assert.That("/cygdrive/c/foo/bar", Is.EqualTo(ToMingwPath "C:/foo/bar"))
    Assert.That("/cygdrive/c/foo", Is.EqualTo(ToMingwPath "/c/foo"))
    Assert.That("/cygdrive/D/foo", Is.EqualTo(ToMingwPath "/D/foo"))
    Assert.That("/var/log", Is.EqualTo(ToMingwPath "/var/log"))
let TesttoMingwPath () =
    Assert.That("/cygdrive/c/", Is.EqualTo(toMingwPath "c:\\"))
    Assert.That("/cygdrive/c/", Is.EqualTo(toMingwPath "C:\\"))
    Assert.That("/cygdrive/c/foo", Is.EqualTo(toMingwPath "C:\\foo"))
    Assert.That("/cygdrive/d/foo", Is.EqualTo(toMingwPath "D:\\foo"))
    Assert.That("/cygdrive/d/Foo", Is.EqualTo(toMingwPath "D:\\Foo"))
    Assert.That("/cygdrive/c/foo/bar/", Is.EqualTo(toMingwPath "C:\\foo\\bar\\"))
    Assert.That("/cygdrive/c/foo/bar/baz.txt", Is.EqualTo(toMingwPath "C:\\foo\\bar\\baz.txt"))
    Assert.That("/cygdrive/c/foo", Is.EqualTo(toMingwPath "C:/foo"))
    Assert.That("/cygdrive/c/foo/bar", Is.EqualTo(toMingwPath "C:/foo/bar"))
    Assert.That("/cygdrive/c/foo", Is.EqualTo(toMingwPath "/c/foo"))
    Assert.That("/cygdrive/D/foo", Is.EqualTo(toMingwPath "/D/foo"))
    Assert.That("/var/log", Is.EqualTo(toMingwPath "/var/log"))

let mysprintf fmt = sprintf fmt

@@ -45,3 +45,15 @@ let TestMylogger () =
  Assert.That("INFO 123", Is.EqualTo(mylogger "%d" 123))
  Assert.That("INFO 123 456", Is.EqualTo(mylogger "%d %d" 123 456))
  Assert.That("INFO 123 456 a", Is.EqualTo(mylogger "%d %d %s" 123 456 "a"))

[<Test>]
let TestStringSplit () =
  let r = "a=b".Split('=')
  Assert.That("a", Is.EqualTo(r.[0]))
  Assert.That("b", Is.EqualTo(r.[1]))
  let r = "a= b".Split('=')
  Assert.That("a", Is.EqualTo(r.[0]))
  Assert.That(" b", Is.EqualTo(r.[1]))
  let r = "a=b=c".Split('=', 2)
  Assert.That("a", Is.EqualTo(r.[0]))
  Assert.That("b=c", Is.EqualTo(r.[1]))
Loading