# Day36 Golang - 檔案路徑 以及Config 的好幫手 Viper ## config配置設定 小時候玩遊戲時,想在一開始就穿上 **LV.999神裝** 或想讓遊戲更快破關,而找有沒有什麼破解方法,解法時常要調整 config 或其他大大小小的設定,但那時一直搞不懂什麼是配置、設定,到底是在配什麼設什麼,以及心裡OS `這東西的配置為什麼會出現在這裡?` 、`別的遊戲的配置怎麼在不同地方?` 、 `啊為什麼參數名稱叫做這個?跟別的遊戲都不一樣` ... 直到開始寫規模稍大的程式後,才發現原來把變數的值 抽出來寫在一個文件或檔案裡面,這包東西就叫做 **config**,這些參數將在程式運行時會被讀進程式中。 另外有一篇關於 Options、Settings、Properties、Configuration、Preferences 在名詞上 [**有何不同**](https://stackoverflow.com/questions/2074384/options-settings-properties-configuration-preferences-when-and-why) 的文章。 ## Viper 套件 Viper套件可以讀取 `JSON`, `TOML`, `YAML` 等常見的設定文件。 ## 安裝 $ go get -u github.com/spf13/viper # 讀取檔案路徑 ## config.toml 這是我的 `config.toml` 設定檔案,底下有三個參數: `Level` 、 `ATK` 、 `DEF` ```toml= Level = 99 ATK = 10000 DEF = 10000 ``` ![config.toml](https://i.imgur.com/QxYuZpE.png) `config.toml` 可以放在專案目錄底下,也可以放在根目錄等等的位置 #### (Win10) ``` C:\Users\User\go\src\github.com\gjlmotea\MyProject01\config.toml ``` 讀取設定檔案有數個方法: ## 用絕對路徑讀取 config.toml ### 讀取方式1 - SetConfigFile 直接給Viper `ConfigFile`的絕對路徑 ```go= func main() { // 在 Windows 中,資料夾目錄是以 `\\` 或者 `/` 區分 viper.SetConfigFile("C:\\Users\\GJLMoTea\\go\\src\\github.com\\gjlmotea\\MyProject01\\config.toml") err := viper.ReadInConfig() // 將檔案讀進 if err != nil { log.Fatalln(err) } atk := viper.Get("ATK") fmt.Println(atk) } ``` ### 讀取方式2 - AddConfigPath 丟目錄名稱給Viper,要他在目錄底下尋找名稱或副檔名 ```go= func main() { viper.AddConfigPath("C:\\Users\\GJLMoTea\\go\\src\\github.com\\gjlmotea\\MyProject01") // 指的是config所在的目錄路徑喔,不用再加上檔名 `config.toml` viper.SetConfigName("config") // 名字、副檔名 擇一使用即可 // viper.SetConfigType("toml") // 名字、副檔名 擇一使用即可 err := viper.ReadInConfig() // 將檔案讀進 if err != nil { log.Fatalln(err) } atk := viper.Get("ATK") fmt.Println(atk) } // Result: // 10000 ``` 使用絕對路徑固然方便,只要預先知道設定檔案的路徑位置就行。 但,在某些情況下config不一定會放在本機的特定位置,此時相對路徑就派上用場了 ## 用相對路徑讀取 config.toml 相對路徑是以 **現在所在的位置** 的角度為出發點 ### 讀取方式1 - SetConfigFile ```go= func main() { viper.SetConfigFile("config.toml") // 可以改成 "./config.toml" 或是 "../MyProject01/config.toml" ,都會指到這個設定檔 err := viper.ReadInConfig() // 將檔案讀進 if err != nil { log.Fatalln(err) } atk := viper.Get("ATK") fmt.Println(atk) } ``` ### 讀取方式2 - AddConfigPath ```go= func main() { viper.AddConfigPath(".") // config所在的目錄路徑 viper.SetConfigName("config") err := viper.ReadInConfig() if err != nil { log.Fatalln(err) } atk := viper.Get("ATK") fmt.Println(atk) } ``` # 坑 - 相對路徑的坑 ### 在哪個目錄下指令,是有不同結果的 在下圖中,第一個橘框框找不到檔案路徑 (~/go/src/github.com/gjlmotea) $ go run MyProject01/main.go open ../MyProject01/config.toml: The system cannot find the path specified. exit status 1 第二個橘框框才能正常運行 (~/go/src/github.com/gjlmotea/MyProject01) $ go run main.go ![go run](https://i.imgur.com/S1eCy4c.png) 為什麼會這樣? 其實將程式改成下面這樣,印出 **目前所在位置** 再執行看看, 就曉得上面為何會找不到config檔了 ```go= func main() { pwd, _ := os.Getwd() fmt.Println(pwd) } ``` ![目前所在位置](https://i.imgur.com/hJLOlc4.png) 也就是說,相對路徑是相對於 **下指令時所在的位置**, 而非相對於(被執行的)程式碼或檔案的位置。 ----------------------- ### 為什麼要這樣做? 為什麼是相對於 目前Terminal的路徑,而非相對於 程式碼或檔案的位置? 通常Server會固定Config的檔案位置 ```go= func main() { pwd, _ := os.Getwd() fmt.Println("pwd:",pwd) abs, err := filepath.Abs(filepath.Dir(os.Args[0])) fmt.Println("abs:", abs) rel, err := filepath.Rel(pwd, abs) if err != nil { log.Fatal(err) } fmt.Println("rel:",rel) viper.AddConfigPath(rel) // config所在的目錄路徑 viper.SetConfigName("config") err = viper.ReadInConfig() if err != nil { log.Fatalln(err) } atk := viper.Get("ATK") fmt.Println(atk) } ``` https://stackoverflow.com/questions/18537257/how-to-get-the-directory-of-the-currently-running-file ### 我想到的解決辦法: ## 這時候怎麼辦 ```go= [Player] Level = 1 HP = 100 MP = 50 ATK = 10 DEF = 3 ```