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
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)
......@@ -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 + "\\"
......@@ -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
......
......@@ -8,6 +8,7 @@
<ItemGroup>
<Compile Include="Lib.fs" />
<Compile Include="ConfigParser.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
......
......@@ -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]))
* COMMENT -*- mode: org -*-
#+Date: 2019-11-12
Time-stamp: <2019-11-12>
Time-stamp: <2019-11-14>
#+STARTUP: content
* notes :entry:
** 2019-11-13 how to run it in dev env?
......@@ -17,6 +17,9 @@ file list works.
ssh transfer works.
remote logging works.
read target from config file works. now I can just run
dotnet run -- -i
** 2019-11-12 docs
- rsync
https://www.samba.org/ftp/rsync/rsync.html
......@@ -90,11 +93,6 @@ it can only support open a namespace.
using the vscode Ionide-fsharp extension.
* current :entry:
**
** 2019-11-14 support mbackup.ini config.
options to support in ini file.
[mbackup]
target=user@host:port/path/
** 2019-11-13 next todos
- DONE fix TODOs in F# code
- DONE add rsync command and arguments for running in windows.
......@@ -275,7 +273,7 @@ target=user@host:port/path/
- there is no enough RAM on B75I3. I will stop bogon VM and run win 10 VM.
-
** 2019-11-12 mbackup for windows
** 2019-11-12 mbackup for windows :featurereq:
- features
- windows installer.
bundles mingw rsync and ssh.
......@@ -299,6 +297,32 @@ target=user@host:port/path/
it contains hostname.
-
* done :entry:
** 2019-11-14 support mbackup.txt config.
config file will have wells config file format.
empty lines and comment lines are ignored.
space around = is ignored.
if there are quotes on value side, it's trimmed.
target=user@host:port/path/
# target = user@host:port/path/
abc.def = some value
abc.def.ghi = 1233.45
foo = yes
env variable will be
TARGET
ABC_DEF
ABC_DEF_GHI
FOO
in F# code,
conf = WellsConfig("C:\path\to\file.txt")
conf.GetStr("target")
conf.GetFloat("abc.def.ghi")
conf.GetBool("foo")
it works.
** 2019-11-13 extra user default list.
Documents is replaced by real path.
Downloads
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment