Gleam CLI client for Tangled (clone of aly.codes/tg)
0

Configure Feed

Select the types of activity you want to include in your feed.

gtg / src / gtg / config.gleam
4.9 kB 178 lines
1//// Configuration: flags > TG_* env > config.toml > defaults. 2 3import envoy 4import filepath 5import gleam/dict 6import gleam/list 7import gleam/option.{type Option, None, Some} 8import gleam/string 9import gtg/sys 10import simplifile 11import tom 12 13pub const default_appview = "https://bobbin.klbr.net" 14 15pub type Settings { 16 Settings(appview: String, account: Option(String), json: Bool) 17} 18 19pub type FlagSettings { 20 FlagSettings( 21 config_path: Option(String), 22 appview: Option(String), 23 account: Option(String), 24 json: Bool, 25 ) 26} 27 28pub fn default_settings() -> Settings { 29 Settings(appview: default_appview, account: None, json: False) 30} 31 32/// Parse known root flags from argv (does not consume subcommand args). 33pub fn parse_flags(args: List(String)) -> FlagSettings { 34 parse_flags_loop(args, FlagSettings(None, None, None, False)) 35} 36 37fn parse_flags_loop(args: List(String), flags: FlagSettings) -> FlagSettings { 38 case args { 39 [] -> flags 40 ["--", ..] -> flags 41 ["--json", ..rest] -> 42 parse_flags_loop(rest, FlagSettings(..flags, json: True)) 43 ["--config", value, ..rest] -> 44 parse_flags_loop(rest, FlagSettings(..flags, config_path: Some(value))) 45 ["--appview", value, ..rest] -> 46 parse_flags_loop(rest, FlagSettings(..flags, appview: Some(value))) 47 ["--account", value, ..rest] -> 48 parse_flags_loop(rest, FlagSettings(..flags, account: Some(value))) 49 [arg, ..rest] -> { 50 case string.split_once(arg, "=") { 51 Ok(#("--config", v)) -> 52 parse_flags_loop(rest, FlagSettings(..flags, config_path: Some(v))) 53 Ok(#("--appview", v)) -> 54 parse_flags_loop(rest, FlagSettings(..flags, appview: Some(v))) 55 Ok(#("--account", v)) -> 56 parse_flags_loop(rest, FlagSettings(..flags, account: Some(v))) 57 _ -> parse_flags_loop(rest, flags) 58 } 59 } 60 } 61} 62 63/// Strip global flags from args, leaving command path + command flags/args. 64pub fn strip_global_flags(args: List(String)) -> List(String) { 65 strip_loop(args, []) 66} 67 68fn strip_loop(args: List(String), acc: List(String)) -> List(String) { 69 case args { 70 [] -> list.reverse(acc) 71 ["--"] -> list.reverse(acc) 72 ["--json", ..rest] -> strip_loop(rest, acc) 73 ["--config", _value, ..rest] -> strip_loop(rest, acc) 74 ["--appview", _value, ..rest] -> strip_loop(rest, acc) 75 ["--account", _value, ..rest] -> strip_loop(rest, acc) 76 [arg, ..rest] -> { 77 case string.starts_with(arg, "--config=") 78 || string.starts_with(arg, "--appview=") 79 || string.starts_with(arg, "--account=") 80 { 81 True -> strip_loop(rest, acc) 82 False -> strip_loop(rest, [arg, ..acc]) 83 } 84 } 85 } 86} 87 88pub fn load(flags: FlagSettings) -> Settings { 89 let file_settings = read_config_file(flags.config_path) 90 let appview = 91 coalesce_string([ 92 flags.appview, 93 env("TG_APPVIEW"), 94 file_settings.appview, 95 Some(default_appview), 96 ]) 97 let account = 98 coalesce_opt([flags.account, env("TG_ACCOUNT"), file_settings.account]) 99 Settings(appview:, account:, json: flags.json) 100} 101 102fn env(name: String) -> Option(String) { 103 case envoy.get(name) { 104 Ok(v) if v != "" -> Some(v) 105 _ -> None 106 } 107} 108 109fn coalesce_string(opts: List(Option(String))) -> String { 110 case opts { 111 [] -> default_appview 112 [Some(v), ..] if v != "" -> v 113 [_, ..rest] -> coalesce_string(rest) 114 } 115} 116 117fn coalesce_opt(opts: List(Option(String))) -> Option(String) { 118 case opts { 119 [] -> None 120 [Some(v), ..] if v != "" -> Some(v) 121 [_, ..rest] -> coalesce_opt(rest) 122 } 123} 124 125type FileSettings { 126 FileSettings(appview: Option(String), account: Option(String)) 127} 128 129fn read_config_file(path: Option(String)) -> FileSettings { 130 let candidates = case path { 131 Some(p) -> [p] 132 None -> [ 133 filepath.join(sys.config_dir(), "config.toml"), 134 // also accept tg config for interoperability 135 filepath.join( 136 case envoy.get("XDG_CONFIG_HOME") { 137 Ok(xdg) if xdg != "" -> filepath.join(xdg, "tg") 138 _ -> 139 case envoy.get("HOME") { 140 Ok(home) -> filepath.join(filepath.join(home, ".config"), "tg") 141 Error(_) -> ".config/tg" 142 } 143 }, 144 "config.toml", 145 ), 146 ] 147 } 148 case list.find_map(candidates, read_one_config) { 149 Ok(s) -> s 150 Error(_) -> FileSettings(None, None) 151 } 152} 153 154fn read_one_config(path: String) -> Result(FileSettings, Nil) { 155 case simplifile.read(path) { 156 Error(_) -> Error(Nil) 157 Ok(body) -> { 158 case tom.parse(body) { 159 Error(_) -> Error(Nil) 160 Ok(doc) -> { 161 let appview = tom_string(doc, "appview") 162 let account = tom_string(doc, "account") 163 Ok(FileSettings(appview:, account:)) 164 } 165 } 166 } 167 } 168} 169 170fn tom_string( 171 doc: dict.Dict(String, tom.Toml), 172 key: String, 173) -> Option(String) { 174 case dict.get(doc, key) { 175 Ok(tom.String(s)) -> Some(s) 176 _ -> None 177 } 178}