Skip to content

Commit

Permalink
Merge pull request #4 from jalbert-dev/color-ui
Browse files Browse the repository at this point in the history
Inline text color support + simple coloration of selected line
  • Loading branch information
jalbert-dev authored Oct 16, 2020
2 parents 4560c72 + 4fad5a6 commit 5cc897a
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CliArguments.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ArgParser =
| [<AltCommandLine("-a"); CustomCommandLine("--config-paths"); Unique>] AppendConfigs of string list
| [<AltCommandLine("-r"); CustomCommandLine("--regions"); Unique>] Regions of string list
| [<AltCommandLine("-p"); CustomCommandLine("--openvpn-path"); Unique>] OpenVpnPath of string
| [<AltCommandLine("-nc"); CustomCommandLine("--no-color"); Unique>] NoColor
with
interface IArgParserTemplate with
member this.Usage =
Expand All @@ -19,6 +20,7 @@ with
| AppendConfigs _ -> "specify any number of paths to files whose contents should be appended to the OpenVPN config"
| Regions _ -> "specify any number of region codes to include in the VPN list (by default, shows all regions)"
| OpenVpnPath _ -> "specify the path to OpenVPN"
| NoColor _ -> "disables color output"

let parseArgs argv =
let parser = ArgumentParser.Create<ArgParser>(checkStructure=true)
Expand Down
40 changes: 22 additions & 18 deletions Config.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Config = {
AllowedRegions: string array;
ConfigPaths: string array;
OpenVpnPath: string;
NoColor: bool;
}

module Config =
Expand All @@ -17,22 +18,25 @@ module Config =

/// Constructs a configuration object from a completed argument parse.
let fromArgs (args: ParseResults<Cli.ArgParser>) = {
DataSource =
args.TryGetResult(<@ Cli.Source @>)
|> Option.defaultValue defaultVpnListSource;

AllowedRegions =
args.TryGetResult(<@ Cli.Regions @>)
|> Option.map List.toArray
|> Option.defaultValue [||]
|> Array.map (fun x -> x.ToLower());

ConfigPaths =
args.TryGetResult(<@ Cli.AppendConfigs @>)
|> Option.map List.toArray
|> Option.defaultValue [||];

OpenVpnPath =
args.TryGetResult(<@ Cli.OpenVpnPath @>)
|> Option.defaultValue DefaultOpenVpnPath
DataSource =
args.TryGetResult(<@ Cli.Source @>)
|> Option.defaultValue defaultVpnListSource;

AllowedRegions =
args.TryGetResult(<@ Cli.Regions @>)
|> Option.map List.toArray
|> Option.defaultValue [||]
|> Array.map (fun x -> x.ToLower());

ConfigPaths =
args.TryGetResult(<@ Cli.AppendConfigs @>)
|> Option.map List.toArray
|> Option.defaultValue [||];

OpenVpnPath =
args.TryGetResult(<@ Cli.OpenVpnPath @>)
|> Option.defaultValue DefaultOpenVpnPath

NoColor =
args.Contains(<@ Cli.NoColor @>)
}
15 changes: 15 additions & 0 deletions DomainTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,18 @@ type ErrorType =
| CsvParseError of string
| InvalidPath of string
| CannotOpenFileBecause of string

// The first 16 codepoints of the Unicode private-use area at 0xE000 are used
// internally as color markers. 0xE000 resets color to default; values 0xE001 to
// 0xE010 map to the values of the System.ConsoleColor enum.
[<Literal>]
let ControlCharRangeStart = 0xE000
[<Literal>]
let ControlCharRangeEnd = 0xE010

let encodeDefaultConsoleColor =
ControlCharRangeStart |> char
let encodeConsoleColor (color: System.ConsoleColor) =
int color + ControlCharRangeStart + 1 |> char
let decodeConsoleColor (c: char) =
int c - ControlCharRangeStart - 1 |> enum<System.ConsoleColor>
4 changes: 3 additions & 1 deletion Gui.fs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ let private lineOf width char =
let private blankLine width = lineOf width ' '

let private renderItem index selected maxWidth (row : VpnList.Row) rtt =
sprintf " [%c] %3d. %s %sms, %s (%s)"
sprintf "%c [%c] %3d. %s %sms, %s (%s)%c"
(if selected then encodeConsoleColor ConsoleColor.Green else encodeDefaultConsoleColor)
(if selected then 'X' else ' ')
index
row.CountryShort
(rtt |> Option.defaultValue (string (int row.Ping)) |> padToLength 3 ' ')
row.``#HostName``
row.IP
encodeDefaultConsoleColor
|> trimToLength maxWidth

let private resultState result state =
Expand Down
20 changes: 14 additions & 6 deletions Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@ let errorToMessage = function

open ProgramFlow.Operators

let private overwriteConsole (str : string) =
Console.SetCursorPosition(0, 0)
Console.WriteLine str
let private consoleOverwriter colorDisabled =
fun (str : string) ->
Console.SetCursorPosition(0, 0)
for c in str do
match c with
| _ when int c >= ControlCharRangeStart && int c <= ControlCharRangeEnd ->
if not colorDisabled then
Console.ForegroundColor <- decodeConsoleColor c
| _ ->
Console.Write c

let parseArguments argv =
match argv |> Cli.parseArgs with
Expand All @@ -31,15 +38,16 @@ let resetConsoleProperties _ =
Console.ResetColor()

/// Shows a menu prompting user to select a VPN from those allowed by given filter predicate.
let promptForSelection filterPredicate rows =
let promptForSelection colorDisabled filterPredicate rows =
Console.CancelKeyPress.Add(resetConsoleProperties)
Console.CursorVisible <- false
Console.Clear()

let drawFunction = consoleOverwriter colorDisabled
let filteredRows = Array.filter filterPredicate rows

let rv =
match Gui.execRowSelectorMenu overwriteConsole filteredRows with
match Gui.execRowSelectorMenu drawFunction filteredRows with
| Some selection -> ContinueData selection
| None -> ProgramFlow.normalExitWithMsg "No VPN selected"

Expand Down Expand Up @@ -107,7 +115,7 @@ let execute (config: Config) =

connectToDataSource config.DataSource
<!> printDataCount
>>= promptForSelection regionFilter
>>= promptForSelection config.NoColor regionFilter
<!> printSelectedVpn
>>= extractOpenVpnConfig
>>= readAndMergeConfigs config.ConfigPaths
Expand Down

0 comments on commit 5cc897a

Please sign in to comment.