From f7b7b63e3f30b4d855a26d550ddf726116c65846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Sun, 2 Jun 2019 18:20:59 -0700 Subject: [PATCH] Add optional config file parser in addition to environment variables --- cli/cli.go | 25 ++- config/config.go | 8 +- config/config_test.go | 371 ++++++++++++++++++++++++------------- config/options.go | 91 +++++++++ config/parser.go | 228 +++++++++++++++-------- config/parser_test.go | 44 ++--- miniflux.1 | 19 +- template/functions_test.go | 64 ++++++- 8 files changed, 599 insertions(+), 251 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index ae94aba2..eba1ee3d 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -24,11 +24,14 @@ const ( flagResetPasswordHelp = "Reset user password" flagResetFeedErrorsHelp = "Clear all feed errors for all users" flagDebugModeHelp = "Show debug logs" + flagConfigFileHelp = "Load configuration file" + flagConfigDumpHelp = "Print parsed configuration values" ) // Parse parses command line arguments. func Parse() { var ( + err error flagInfo bool flagVersion bool flagMigrate bool @@ -37,6 +40,8 @@ func Parse() { flagResetPassword bool flagResetFeedErrors bool flagDebugMode bool + flagConfigFile string + flagConfigDump bool ) flag.BoolVar(&flagInfo, "info", false, flagInfoHelp) @@ -49,12 +54,30 @@ func Parse() { flag.BoolVar(&flagResetPassword, "reset-password", false, flagResetPasswordHelp) flag.BoolVar(&flagResetFeedErrors, "reset-feed-errors", false, flagResetFeedErrorsHelp) flag.BoolVar(&flagDebugMode, "debug", false, flagDebugModeHelp) + flag.StringVar(&flagConfigFile, "config-file", "", flagConfigFileHelp) + flag.StringVar(&flagConfigFile, "c", "", flagConfigFileHelp) + flag.BoolVar(&flagConfigDump, "config-dump", false, flagConfigDumpHelp) flag.Parse() - if err := config.ParseConfig(); err != nil { + cfg := config.NewParser() + + if flagConfigFile != "" { + config.Opts, err = cfg.ParseFile(flagConfigFile) + if err != nil { + logger.Fatal("%v", err) + } + } + + config.Opts, err = cfg.ParseEnvironmentVariables() + if err != nil { logger.Fatal("%v", err) } + if flagConfigDump { + fmt.Print(config.Opts) + return + } + if flagDebugMode || config.Opts.HasDebugMode() { logger.EnableDebug() } diff --git a/config/config.go b/config/config.go index 53bd7085..60909748 100644 --- a/config/config.go +++ b/config/config.go @@ -4,11 +4,5 @@ package config // import "miniflux.app/config" -// Opts contains configuration options after parsing. +// Opts holds parsed configuration options. var Opts *Options - -// ParseConfig parses configuration options. -func ParseConfig() (err error) { - Opts, err = parse() - return err -} diff --git a/config/config_test.go b/config/config_test.go index 6b6cdf2e..0e10345a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,6 +5,7 @@ package config // import "miniflux.app/config" import ( + "io/ioutil" "os" "testing" ) @@ -13,9 +14,10 @@ func TestDebugModeOn(t *testing.T) { os.Clearenv() os.Setenv("DEBUG", "1") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } if !opts.HasDebugMode() { @@ -26,9 +28,10 @@ func TestDebugModeOn(t *testing.T) { func TestDebugModeOff(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } if opts.HasDebugMode() { @@ -40,9 +43,10 @@ func TestCustomBaseURL(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example.org") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } if opts.BaseURL() != "http://example.org" { @@ -62,9 +66,10 @@ func TestCustomBaseURLWithTrailingSlash(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example.org/folder/") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } if opts.BaseURL() != "http://example.org/folder" { @@ -84,7 +89,7 @@ func TestBaseURLWithoutScheme(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "example.org/folder/") - _, err := parse() + _, err := NewParser().ParseEnvironmentVariables() if err == nil { t.Fatalf(`Parsing must fail`) } @@ -94,7 +99,7 @@ func TestBaseURLWithInvalidScheme(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "ftp://example.org/folder/") - _, err := parse() + _, err := NewParser().ParseEnvironmentVariables() if err == nil { t.Fatalf(`Parsing must fail`) } @@ -104,7 +109,7 @@ func TestInvalidBaseURL(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example|org") - _, err := parse() + _, err := NewParser().ParseEnvironmentVariables() if err == nil { t.Fatalf(`Parsing must fail`) } @@ -113,9 +118,10 @@ func TestInvalidBaseURL(t *testing.T) { func TestDefaultBaseURL(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } if opts.BaseURL() != defaultBaseURL { @@ -135,41 +141,52 @@ func TestDatabaseURL(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_URL", "foobar") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "foobar" result := opts.DatabaseURL() if result != expected { - t.Fatalf(`Unexpected DATABASE_URL value, got %q instead of %q`, result, expected) + t.Errorf(`Unexpected DATABASE_URL value, got %q instead of %q`, result, expected) + } + + if opts.IsDefaultDatabaseURL() { + t.Errorf(`This is not the default database URL and it should returns false`) } } func TestDefaultDatabaseURLValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultDatabaseURL result := opts.DatabaseURL() if result != expected { - t.Fatalf(`Unexpected DATABASE_URL value, got %q instead of %q`, result, expected) + t.Errorf(`Unexpected DATABASE_URL value, got %q instead of %q`, result, expected) + } + + if !opts.IsDefaultDatabaseURL() { + t.Errorf(`This is the default database URL and it should returns true`) } } func TestDefaultDatabaseMaxConnsValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultDatabaseMaxConns @@ -184,9 +201,10 @@ func TestDatabaseMaxConns(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_MAX_CONNS", "42") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := 42 @@ -200,9 +218,10 @@ func TestDatabaseMaxConns(t *testing.T) { func TestDefaultDatabaseMinConnsValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultDatabaseMinConns @@ -217,9 +236,10 @@ func TestDatabaseMinConns(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_MIN_CONNS", "42") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := 42 @@ -234,9 +254,10 @@ func TestListenAddr(t *testing.T) { os.Clearenv() os.Setenv("LISTEN_ADDR", "foobar") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "foobar" @@ -252,9 +273,10 @@ func TestListenAddrWithPortDefined(t *testing.T) { os.Setenv("PORT", "3000") os.Setenv("LISTEN_ADDR", "foobar") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := ":3000" @@ -268,9 +290,10 @@ func TestListenAddrWithPortDefined(t *testing.T) { func TestDefaultListenAddrValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultListenAddr @@ -285,9 +308,10 @@ func TestCertFile(t *testing.T) { os.Clearenv() os.Setenv("CERT_FILE", "foobar") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "foobar" @@ -301,9 +325,10 @@ func TestCertFile(t *testing.T) { func TestDefaultCertFileValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultCertFile @@ -318,9 +343,10 @@ func TestKeyFile(t *testing.T) { os.Clearenv() os.Setenv("KEY_FILE", "foobar") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "foobar" @@ -334,9 +360,10 @@ func TestKeyFile(t *testing.T) { func TestDefaultKeyFileValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultKeyFile @@ -351,9 +378,10 @@ func TestCertDomain(t *testing.T) { os.Clearenv() os.Setenv("CERT_DOMAIN", "example.org") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "example.org" @@ -367,9 +395,10 @@ func TestCertDomain(t *testing.T) { func TestDefaultCertDomainValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultCertDomain @@ -384,9 +413,10 @@ func TestCertCache(t *testing.T) { os.Clearenv() os.Setenv("CERT_CACHE", "foobar") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "foobar" @@ -400,9 +430,10 @@ func TestCertCache(t *testing.T) { func TestDefaultCertCacheValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultCertCache @@ -416,9 +447,10 @@ func TestDefaultCertCacheValue(t *testing.T) { func TestDefaultCleanupFrequencyValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultCleanupFrequency @@ -433,9 +465,10 @@ func TestCleanupFrequency(t *testing.T) { os.Clearenv() os.Setenv("CLEANUP_FREQUENCY", "42") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := 42 @@ -449,9 +482,10 @@ func TestCleanupFrequency(t *testing.T) { func TestDefaultWorkerPoolSizeValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultWorkerPoolSize @@ -466,9 +500,10 @@ func TestWorkerPoolSize(t *testing.T) { os.Clearenv() os.Setenv("WORKER_POOL_SIZE", "42") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := 42 @@ -482,9 +517,10 @@ func TestWorkerPoolSize(t *testing.T) { func TestDefautPollingFrequencyValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultPollingFrequency @@ -499,9 +535,10 @@ func TestPollingFrequency(t *testing.T) { os.Clearenv() os.Setenv("POLLING_FREQUENCY", "42") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := 42 @@ -515,9 +552,10 @@ func TestPollingFrequency(t *testing.T) { func TestDefaultBatchSizeValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultBatchSize @@ -532,9 +570,10 @@ func TestBatchSize(t *testing.T) { os.Clearenv() os.Setenv("BATCH_SIZE", "42") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := 42 @@ -548,9 +587,10 @@ func TestBatchSize(t *testing.T) { func TestOAuth2UserCreationWhenUnset(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := false @@ -565,9 +605,10 @@ func TestOAuth2UserCreationAdmin(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_USER_CREATION", "1") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := true @@ -582,9 +623,10 @@ func TestOAuth2ClientID(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_CLIENT_ID", "foobar") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "foobar" @@ -598,9 +640,10 @@ func TestOAuth2ClientID(t *testing.T) { func TestDefaultOAuth2ClientIDValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultOAuth2ClientID @@ -615,9 +658,10 @@ func TestOAuth2ClientSecret(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_CLIENT_SECRET", "secret") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "secret" @@ -631,9 +675,10 @@ func TestOAuth2ClientSecret(t *testing.T) { func TestDefaultOAuth2ClientSecretValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultOAuth2ClientSecret @@ -648,9 +693,10 @@ func TestOAuth2RedirectURL(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_REDIRECT_URL", "http://example.org") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "http://example.org" @@ -664,9 +710,10 @@ func TestOAuth2RedirectURL(t *testing.T) { func TestDefaultOAuth2RedirectURLValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultOAuth2RedirectURL @@ -681,9 +728,10 @@ func TestOAuth2Provider(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_PROVIDER", "google") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "google" @@ -697,9 +745,10 @@ func TestOAuth2Provider(t *testing.T) { func TestDefaultOAuth2ProviderValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultOAuth2Provider @@ -713,9 +762,10 @@ func TestDefaultOAuth2ProviderValue(t *testing.T) { func TestHSTSWhenUnset(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := true @@ -730,9 +780,10 @@ func TestHSTS(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_HSTS", "1") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := false @@ -746,9 +797,10 @@ func TestHSTS(t *testing.T) { func TestDisableHTTPServiceWhenUnset(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := true @@ -763,9 +815,10 @@ func TestDisableHTTPService(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_HTTP_SERVICE", "1") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := false @@ -779,9 +832,10 @@ func TestDisableHTTPService(t *testing.T) { func TestDisableSchedulerServiceWhenUnset(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := true @@ -796,9 +850,10 @@ func TestDisableSchedulerService(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_SCHEDULER_SERVICE", "1") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := false @@ -813,9 +868,10 @@ func TestArchiveReadDays(t *testing.T) { os.Clearenv() os.Setenv("ARCHIVE_READ_DAYS", "7") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := 7 @@ -829,9 +885,10 @@ func TestArchiveReadDays(t *testing.T) { func TestRunMigrationsWhenUnset(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := false @@ -846,9 +903,10 @@ func TestRunMigrations(t *testing.T) { os.Clearenv() os.Setenv("RUN_MIGRATIONS", "yes") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := true @@ -862,9 +920,10 @@ func TestRunMigrations(t *testing.T) { func TestCreateAdminWhenUnset(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := false @@ -879,9 +938,10 @@ func TestCreateAdmin(t *testing.T) { os.Clearenv() os.Setenv("CREATE_ADMIN", "true") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := true @@ -896,9 +956,10 @@ func TestPocketConsumerKeyFromEnvVariable(t *testing.T) { os.Clearenv() os.Setenv("POCKET_CONSUMER_KEY", "something") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "something" @@ -912,9 +973,10 @@ func TestPocketConsumerKeyFromEnvVariable(t *testing.T) { func TestPocketConsumerKeyFromUserPrefs(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "default" @@ -929,9 +991,10 @@ func TestProxyImages(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "all") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := "all" @@ -945,9 +1008,10 @@ func TestProxyImages(t *testing.T) { func TestDefaultProxyImagesValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultProxyImages @@ -961,9 +1025,10 @@ func TestDefaultProxyImagesValue(t *testing.T) { func TestHTTPSOff(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } if opts.HTTPS { @@ -975,9 +1040,10 @@ func TestHTTPSOn(t *testing.T) { os.Clearenv() os.Setenv("HTTPS", "on") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } if !opts.HTTPS { @@ -989,9 +1055,10 @@ func TestHTTPClientTimeout(t *testing.T) { os.Clearenv() os.Setenv("HTTP_CLIENT_TIMEOUT", "42") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := 42 @@ -1005,9 +1072,10 @@ func TestHTTPClientTimeout(t *testing.T) { func TestDefaultHTTPClientTimeoutValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := defaultHTTPClientTimeout @@ -1022,9 +1090,10 @@ func TestHTTPClientMaxBodySize(t *testing.T) { os.Clearenv() os.Setenv("HTTP_CLIENT_MAX_BODY_SIZE", "42") - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := int64(42 * 1024 * 1024) @@ -1038,9 +1107,10 @@ func TestHTTPClientMaxBodySize(t *testing.T) { func TestDefaultHTTPClientMaxBodySizeValue(t *testing.T) { os.Clearenv() - opts, err := parse() + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() if err != nil { - t.Fatalf(`Parsing failure: %q`, err) + t.Fatalf(`Parsing failure: %v`, err) } expected := int64(defaultHTTPClientMaxBodySize * 1024 * 1024) @@ -1050,3 +1120,50 @@ func TestDefaultHTTPClientMaxBodySizeValue(t *testing.T) { t.Fatalf(`Unexpected HTTP_CLIENT_MAX_BODY_SIZE value, got %d instead of %d`, result, expected) } } + +func TestParseConfigFile(t *testing.T) { + content := []byte(` + # This is a comment + +DEBUG = yes + + POCKET_CONSUMER_KEY= >#1234 + +Invalid text +`) + + tmpfile, err := ioutil.TempFile(".", "miniflux.*.unit_test.conf") + if err != nil { + t.Fatal(err) + } + + if _, err := tmpfile.Write(content); err != nil { + t.Fatal(err) + } + + os.Clearenv() + + parser := NewParser() + opts, err := parser.ParseFile(tmpfile.Name()) + if err != nil { + t.Errorf(`Parsing failure: %v`, err) + } + + if opts.HasDebugMode() != true { + t.Errorf(`Unexpected debug mode value, got "%v"`, opts.HasDebugMode()) + } + + expected := ">#1234" + result := opts.PocketConsumerKey("default") + if result != expected { + t.Errorf(`Unexpected POCKET_CONSUMER_KEY value, got %q instead of %q`, result, expected) + } + + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + if err := os.Remove(tmpfile.Name()); err != nil { + t.Fatal(err) + } +} diff --git a/config/options.go b/config/options.go index 32105e9b..06d3edde 100644 --- a/config/options.go +++ b/config/options.go @@ -4,11 +4,24 @@ package config // import "miniflux.app/config" +import ( + "fmt" + "strings" +) + const ( + defaultHTTPS = false + defaultHSTS = true + defaultHTTPService = true + defaultSchedulerService = true + defaultDebug = false defaultBaseURL = "http://localhost" + defaultRootURL = "http://localhost" + defaultBasePath = "" defaultWorkerPoolSize = 5 defaultPollingFrequency = 60 defaultBatchSize = 10 + defaultRunMigrations = false defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable" defaultDatabaseMaxConns = 20 defaultDatabaseMinConns = 1 @@ -20,10 +33,13 @@ const ( defaultCertCache = "/tmp/cert_cache" defaultCleanupFrequency = 24 defaultProxyImages = "http-only" + defaultCreateAdmin = false + defaultOAuth2UserCreation = false defaultOAuth2ClientID = "" defaultOAuth2ClientSecret = "" defaultOAuth2RedirectURL = "" defaultOAuth2Provider = "" + defaultPocketConsumerKey = "" defaultHTTPClientTimeout = 20 defaultHTTPClientMaxBodySize = 15 ) @@ -64,6 +80,44 @@ type Options struct { httpClientMaxBodySize int64 } +// NewOptions returns Options with default values. +func NewOptions() *Options { + return &Options{ + HTTPS: defaultHTTPS, + hsts: defaultHSTS, + httpService: defaultHTTPService, + schedulerService: defaultSchedulerService, + debug: defaultDebug, + baseURL: defaultBaseURL, + rootURL: defaultRootURL, + basePath: defaultBasePath, + databaseURL: defaultDatabaseURL, + databaseMaxConns: defaultDatabaseMaxConns, + databaseMinConns: defaultDatabaseMinConns, + runMigrations: defaultRunMigrations, + listenAddr: defaultListenAddr, + certFile: defaultCertFile, + certDomain: defaultCertDomain, + certCache: defaultCertCache, + certKeyFile: defaultKeyFile, + cleanupFrequency: defaultCleanupFrequency, + archiveReadDays: defaultArchiveReadDays, + pollingFrequency: defaultPollingFrequency, + batchSize: defaultBatchSize, + workerPoolSize: defaultWorkerPoolSize, + createAdmin: defaultCreateAdmin, + proxyImages: defaultProxyImages, + oauth2UserCreationAllowed: defaultOAuth2UserCreation, + oauth2ClientID: defaultOAuth2ClientID, + oauth2ClientSecret: defaultOAuth2ClientSecret, + oauth2RedirectURL: defaultOAuth2RedirectURL, + oauth2Provider: defaultOAuth2Provider, + pocketConsumerKey: defaultPocketConsumerKey, + httpClientTimeout: defaultHTTPClientTimeout, + httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024, + } +} + // HasDebugMode returns true if debug mode is enabled. func (o *Options) HasDebugMode() bool { return o.debug @@ -226,3 +280,40 @@ func (o *Options) HTTPClientTimeout() int { func (o *Options) HTTPClientMaxBodySize() int64 { return o.httpClientMaxBodySize } + +func (o *Options) String() string { + var builder strings.Builder + builder.WriteString(fmt.Sprintf("DEBUG: %v\n", o.debug)) + builder.WriteString(fmt.Sprintf("HTTP_SERVICE: %v\n", o.httpService)) + builder.WriteString(fmt.Sprintf("SCHEDULER_SERVICE: %v\n", o.schedulerService)) + builder.WriteString(fmt.Sprintf("HTTPS: %v\n", o.HTTPS)) + builder.WriteString(fmt.Sprintf("HSTS: %v\n", o.hsts)) + builder.WriteString(fmt.Sprintf("BASE_URL: %v\n", o.baseURL)) + builder.WriteString(fmt.Sprintf("ROOT_URL: %v\n", o.rootURL)) + builder.WriteString(fmt.Sprintf("BASE_PATH: %v\n", o.basePath)) + builder.WriteString(fmt.Sprintf("LISTEN_ADDR: %v\n", o.listenAddr)) + builder.WriteString(fmt.Sprintf("DATABASE_URL: %v\n", o.databaseURL)) + builder.WriteString(fmt.Sprintf("DATABASE_MAX_CONNS: %v\n", o.databaseMaxConns)) + builder.WriteString(fmt.Sprintf("DATABASE_MIN_CONNS: %v\n", o.databaseMinConns)) + builder.WriteString(fmt.Sprintf("RUN_MIGRATIONS: %v\n", o.runMigrations)) + builder.WriteString(fmt.Sprintf("CERT_FILE: %v\n", o.certFile)) + builder.WriteString(fmt.Sprintf("KEY_FILE: %v\n", o.certKeyFile)) + builder.WriteString(fmt.Sprintf("CERT_DOMAIN: %v\n", o.certDomain)) + builder.WriteString(fmt.Sprintf("CERT_CACHE: %v\n", o.certCache)) + builder.WriteString(fmt.Sprintf("CLEANUP_FREQUENCY: %v\n", o.cleanupFrequency)) + builder.WriteString(fmt.Sprintf("WORKER_POOL_SIZE: %v\n", o.workerPoolSize)) + builder.WriteString(fmt.Sprintf("POLLING_FREQUENCY: %v\n", o.pollingFrequency)) + builder.WriteString(fmt.Sprintf("BATCH_SIZE: %v\n", o.batchSize)) + builder.WriteString(fmt.Sprintf("ARCHIVE_READ_DAYS: %v\n", o.archiveReadDays)) + builder.WriteString(fmt.Sprintf("PROXY_IMAGES: %v\n", o.proxyImages)) + builder.WriteString(fmt.Sprintf("CREATE_ADMIN: %v\n", o.createAdmin)) + builder.WriteString(fmt.Sprintf("POCKET_CONSUMER_KEY: %v\n", o.pocketConsumerKey)) + builder.WriteString(fmt.Sprintf("OAUTH2_USER_CREATION: %v\n", o.oauth2UserCreationAllowed)) + builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID)) + builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_SECRET: %v\n", o.oauth2ClientSecret)) + builder.WriteString(fmt.Sprintf("OAUTH2_REDIRECT_URL: %v\n", o.oauth2RedirectURL)) + builder.WriteString(fmt.Sprintf("OAUTH2_PROVIDER: %v\n", o.oauth2Provider)) + builder.WriteString(fmt.Sprintf("HTTP_CLIENT_TIMEOUT: %v\n", o.httpClientTimeout)) + builder.WriteString(fmt.Sprintf("HTTP_CLIENT_MAX_BODY_SIZE: %v\n", o.httpClientMaxBodySize)) + return builder.String() +} diff --git a/config/parser.go b/config/parser.go index b2ed2e76..6de45504 100644 --- a/config/parser.go +++ b/config/parser.go @@ -5,113 +5,184 @@ package config // import "miniflux.app/config" import ( + "bufio" "errors" "fmt" - "net/url" + "io" + url_parser "net/url" "os" "strconv" "strings" ) -func parse() (opts *Options, err error) { - opts = &Options{} - opts.baseURL, opts.rootURL, opts.basePath, err = parseBaseURL() +// Parser handles configuration parsing. +type Parser struct { + opts *Options +} + +// NewParser returns a new Parser. +func NewParser() *Parser { + return &Parser{ + opts: NewOptions(), + } +} + +// ParseEnvironmentVariables loads configuration values from environment variables. +func (p *Parser) ParseEnvironmentVariables() (*Options, error) { + err := p.parseLines(os.Environ()) if err != nil { return nil, err } - - opts.debug = getBooleanValue("DEBUG") - opts.listenAddr = parseListenAddr() - - opts.databaseURL = getStringValue("DATABASE_URL", defaultDatabaseURL) - opts.databaseMaxConns = getIntValue("DATABASE_MAX_CONNS", defaultDatabaseMaxConns) - opts.databaseMinConns = getIntValue("DATABASE_MIN_CONNS", defaultDatabaseMinConns) - opts.runMigrations = getBooleanValue("RUN_MIGRATIONS") - - opts.hsts = !getBooleanValue("DISABLE_HSTS") - opts.HTTPS = getBooleanValue("HTTPS") - - opts.schedulerService = !getBooleanValue("DISABLE_SCHEDULER_SERVICE") - opts.httpService = !getBooleanValue("DISABLE_HTTP_SERVICE") - - opts.certFile = getStringValue("CERT_FILE", defaultCertFile) - opts.certKeyFile = getStringValue("KEY_FILE", defaultKeyFile) - opts.certDomain = getStringValue("CERT_DOMAIN", defaultCertDomain) - opts.certCache = getStringValue("CERT_CACHE", defaultCertCache) - - opts.cleanupFrequency = getIntValue("CLEANUP_FREQUENCY", defaultCleanupFrequency) - opts.workerPoolSize = getIntValue("WORKER_POOL_SIZE", defaultWorkerPoolSize) - opts.pollingFrequency = getIntValue("POLLING_FREQUENCY", defaultPollingFrequency) - opts.batchSize = getIntValue("BATCH_SIZE", defaultBatchSize) - opts.archiveReadDays = getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays) - opts.proxyImages = getStringValue("PROXY_IMAGES", defaultProxyImages) - opts.createAdmin = getBooleanValue("CREATE_ADMIN") - opts.pocketConsumerKey = getStringValue("POCKET_CONSUMER_KEY", "") - - opts.oauth2UserCreationAllowed = getBooleanValue("OAUTH2_USER_CREATION") - opts.oauth2ClientID = getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID) - opts.oauth2ClientSecret = getStringValue("OAUTH2_CLIENT_SECRET", defaultOAuth2ClientSecret) - opts.oauth2RedirectURL = getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL) - opts.oauth2Provider = getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider) - - opts.httpClientTimeout = getIntValue("HTTP_CLIENT_TIMEOUT", defaultHTTPClientTimeout) - opts.httpClientMaxBodySize = int64(getIntValue("HTTP_CLIENT_MAX_BODY_SIZE", defaultHTTPClientMaxBodySize) * 1024 * 1024) - - return opts, nil + return p.opts, nil } -func parseBaseURL() (string, string, string, error) { - baseURL := os.Getenv("BASE_URL") - if baseURL == "" { - return defaultBaseURL, defaultBaseURL, "", nil +// ParseFile loads configuration values from a local file. +func (p *Parser) ParseFile(filename string) (*Options, error) { + fp, err := os.Open(filename) + if err != nil { + return nil, err + } + defer fp.Close() + + err = p.parseLines(p.parseFileContent(fp)) + if err != nil { + return nil, err + } + return p.opts, nil +} + +func (p *Parser) parseFileContent(r io.Reader) (lines []string) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 { + lines = append(lines, line) + } + } + return lines +} + +func (p *Parser) parseLines(lines []string) (err error) { + var port string + + for _, line := range lines { + fields := strings.SplitN(line, "=", 2) + key := strings.TrimSpace(fields[0]) + value := strings.TrimSpace(fields[1]) + + switch key { + case "DEBUG": + p.opts.debug = parseBool(value, defaultDebug) + case "BASE_URL": + p.opts.baseURL, p.opts.rootURL, p.opts.basePath, err = parseBaseURL(value) + if err != nil { + return err + } + case "PORT": + port = value + case "LISTEN_ADDR": + p.opts.listenAddr = parseString(value, defaultListenAddr) + case "DATABASE_URL": + p.opts.databaseURL = parseString(value, defaultDatabaseURL) + case "DATABASE_MAX_CONNS": + p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns) + case "DATABASE_MIN_CONNS": + p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns) + case "RUN_MIGRATIONS": + p.opts.runMigrations = parseBool(value, defaultRunMigrations) + case "DISABLE_HSTS": + p.opts.hsts = !parseBool(value, defaultHSTS) + case "HTTPS": + p.opts.HTTPS = parseBool(value, defaultHTTPS) + case "DISABLE_SCHEDULER_SERVICE": + p.opts.schedulerService = !parseBool(value, defaultSchedulerService) + case "DISABLE_HTTP_SERVICE": + p.opts.httpService = !parseBool(value, defaultHTTPService) + case "CERT_FILE": + p.opts.certFile = parseString(value, defaultCertFile) + case "KEY_FILE": + p.opts.certKeyFile = parseString(value, defaultKeyFile) + case "CERT_DOMAIN": + p.opts.certDomain = parseString(value, defaultCertDomain) + case "CERT_CACHE": + p.opts.certCache = parseString(value, defaultCertCache) + case "CLEANUP_FREQUENCY": + p.opts.cleanupFrequency = parseInt(value, defaultCleanupFrequency) + case "WORKER_POOL_SIZE": + p.opts.workerPoolSize = parseInt(value, defaultWorkerPoolSize) + case "POLLING_FREQUENCY": + p.opts.pollingFrequency = parseInt(value, defaultPollingFrequency) + case "BATCH_SIZE": + p.opts.batchSize = parseInt(value, defaultBatchSize) + case "ARCHIVE_READ_DAYS": + p.opts.archiveReadDays = parseInt(value, defaultArchiveReadDays) + case "PROXY_IMAGES": + p.opts.proxyImages = parseString(value, defaultProxyImages) + case "CREATE_ADMIN": + p.opts.createAdmin = parseBool(value, defaultCreateAdmin) + case "POCKET_CONSUMER_KEY": + p.opts.pocketConsumerKey = parseString(value, defaultPocketConsumerKey) + case "OAUTH2_USER_CREATION": + p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation) + case "OAUTH2_CLIENT_ID": + p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID) + case "OAUTH2_CLIENT_SECRET": + p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret) + case "OAUTH2_REDIRECT_URL": + p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL) + case "OAUTH2_PROVIDER": + p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider) + case "HTTP_CLIENT_TIMEOUT": + p.opts.httpClientTimeout = parseInt(value, defaultHTTPClientTimeout) + case "HTTP_CLIENT_MAX_BODY_SIZE": + p.opts.httpClientMaxBodySize = int64(parseInt(value, defaultHTTPClientMaxBodySize) * 1024 * 1024) + } } - if baseURL[len(baseURL)-1:] == "/" { - baseURL = baseURL[:len(baseURL)-1] + if port != "" { + p.opts.listenAddr = ":" + port + } + return nil +} + +func parseBaseURL(value string) (string, string, string, error) { + if value == "" { + return defaultBaseURL, defaultRootURL, "", nil } - u, err := url.Parse(baseURL) + if value[len(value)-1:] == "/" { + value = value[:len(value)-1] + } + + url, err := url_parser.Parse(value) if err != nil { return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err) } - scheme := strings.ToLower(u.Scheme) + scheme := strings.ToLower(url.Scheme) if scheme != "https" && scheme != "http" { return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https") } - basePath := u.Path - u.Path = "" - return baseURL, u.String(), basePath, nil + basePath := url.Path + url.Path = "" + return value, url.String(), basePath, nil } -func parseListenAddr() string { - if port := os.Getenv("PORT"); port != "" { - return ":" + port - } - - return getStringValue("LISTEN_ADDR", defaultListenAddr) -} - -func getBooleanValue(key string) bool { - value := strings.ToLower(os.Getenv(key)) - if value == "1" || value == "yes" || value == "true" || value == "on" { - return true - } - return false -} - -func getStringValue(key, fallback string) string { - value := os.Getenv(key) +func parseBool(value string, fallback bool) bool { if value == "" { return fallback } - return value + value = strings.ToLower(value) + if value == "1" || value == "yes" || value == "true" || value == "on" { + return true + } + + return false } -func getIntValue(key string, fallback int) int { - value := os.Getenv(key) +func parseInt(value string, fallback int) int { if value == "" { return fallback } @@ -123,3 +194,10 @@ func getIntValue(key string, fallback int) int { return v } + +func parseString(value string, fallback string) string { + if value == "" { + return fallback + } + return value +} diff --git a/config/parser_test.go b/config/parser_test.go index ba454b4b..8f896c12 100644 --- a/config/parser_test.go +++ b/config/parser_test.go @@ -5,20 +5,12 @@ package config // import "miniflux.app/config" import ( - "os" "testing" ) -func TestGetBooleanValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getBooleanValue("MY_TEST_VARIABLE") { - t.Errorf(`Unset variables should returns false`) - } -} - -func TestGetBooleanValue(t *testing.T) { +func TestParseBoolValue(t *testing.T) { scenarios := map[string]bool{ - "": false, + "": true, "1": true, "Yes": true, "yes": true, @@ -31,49 +23,39 @@ func TestGetBooleanValue(t *testing.T) { } for input, expected := range scenarios { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", input) - result := getBooleanValue("MY_TEST_VARIABLE") + result := parseBool(input, true) if result != expected { t.Errorf(`Unexpected result for %q, got %v instead of %v`, input, result, expected) } } } -func TestGetStringValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "defaultValue" { +func TestParseStringValueWithUnsetVariable(t *testing.T) { + if parseString("", "defaultValue") != "defaultValue" { t.Errorf(`Unset variables should returns the default value`) } } -func TestGetStringValue(t *testing.T) { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "test") - if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "test" { +func TestParseStringValue(t *testing.T) { + if parseString("test", "defaultValue") != "test" { t.Errorf(`Defined variables should returns the specified value`) } } -func TestGetIntValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getIntValue("MY_TEST_VARIABLE", 42) != 42 { +func TestParseIntValueWithUnsetVariable(t *testing.T) { + if parseInt("", 42) != 42 { t.Errorf(`Unset variables should returns the default value`) } } -func TestGetIntValueWithInvalidInput(t *testing.T) { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "invalid integer") - if getIntValue("MY_TEST_VARIABLE", 42) != 42 { +func TestParseIntValueWithInvalidInput(t *testing.T) { + if parseInt("invalid integer", 42) != 42 { t.Errorf(`Invalid integer should returns the default value`) } } -func TestGetIntValue(t *testing.T) { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "2018") - if getIntValue("MY_TEST_VARIABLE", 42) != 2018 { +func TestParseIntValue(t *testing.T) { + if parseInt("2018", 42) != 2018 { t.Errorf(`Defined variables should returns the specified value`) } } diff --git a/miniflux.1 b/miniflux.1 index 911732df..d9e7616a 100644 --- a/miniflux.1 +++ b/miniflux.1 @@ -5,14 +5,29 @@ miniflux \- Minimalist and opinionated feed reader .SH SYNOPSIS -\fBminiflux\fR [-vi] [-create-admin] [-debug] [-flush-sessions] [-info] [-migrate] - [-reset-feed-errors] [-reset-password] [-version] +\fBminiflux\fR [-vic] [-create-admin] [-debug] [-flush-sessions] [-info] [-migrate] + [-reset-feed-errors] [-reset-password] [-version] [-config-file] [-config-dump] .SH DESCRIPTION \fBminiflux\fR is a minimalist and opinionated feed reader. .SH OPTIONS .PP +.B \-c +.RS 4 +Load configuration file\&. +.RE +.PP +.B \-config-file +.RS 4 +Load configuration file\&. +.RE +.PP +.B \-config-dump +.RS 4 +Print parsed configuration values\&. +.RE +.PP .B \-create-admin .RS 4 Create admin user\&. diff --git a/template/functions_test.go b/template/functions_test.go index ff563b27..5e373196 100644 --- a/template/functions_test.go +++ b/template/functions_test.go @@ -134,7 +134,13 @@ func TestElapsedTime(t *testing.T) { func TestProxyFilterWithHttpDefault(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "http-only") - config.ParseConfig() + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") @@ -151,7 +157,13 @@ func TestProxyFilterWithHttpDefault(t *testing.T) { func TestProxyFilterWithHttpsDefault(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "http-only") - config.ParseConfig() + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") @@ -168,7 +180,13 @@ func TestProxyFilterWithHttpsDefault(t *testing.T) { func TestProxyFilterWithHttpNever(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "none") - config.ParseConfig() + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") @@ -185,7 +203,13 @@ func TestProxyFilterWithHttpNever(t *testing.T) { func TestProxyFilterWithHttpsNever(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "none") - config.ParseConfig() + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") @@ -202,7 +226,13 @@ func TestProxyFilterWithHttpsNever(t *testing.T) { func TestProxyFilterWithHttpAlways(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "all") - config.ParseConfig() + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") @@ -219,7 +249,13 @@ func TestProxyFilterWithHttpAlways(t *testing.T) { func TestProxyFilterWithHttpsAlways(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "all") - config.ParseConfig() + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") @@ -236,7 +272,13 @@ func TestProxyFilterWithHttpsAlways(t *testing.T) { func TestProxyFilterWithHttpInvalid(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "invalid") - config.ParseConfig() + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") @@ -253,7 +295,13 @@ func TestProxyFilterWithHttpInvalid(t *testing.T) { func TestProxyFilterWithHttpsInvalid(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "invalid") - config.ParseConfig() + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")