Repository: dolthub/dolt
File: go/libraries/doltcore/servercfg/env_interpolate.go (Go)

Code before:
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package servercfg

import (
	"fmt"
	"os"
)

// envLookupFunc returns (value, true) if the env var exists.
// Note that an env var may exist but be empty.
type envLookupFunc func(string) (string, bool)

type envPlaceholder struct {
	varName      string
	def          []byte
	hasDefault   bool
	closingBrace int
}

// interpolateEnv expands environment variable placeholders in |data|.
//
// Supported syntax:
// - ${VAR}            : expands to VAR's value; error if unset or empty
// - ${VAR:-default}   : expands to VAR's value if set and non-empty; otherwise to default
// - $$               : escapes to a literal '$'
//
// Notes:
// - Expansion is applied to the input text only (env var values are inserted literally).
// - Default expressions are expanded (i.e. nested placeholders inside defaults are supported).
func interpolateEnv(data []byte, lookup envLookupFunc) ([]byte, error) {
	if lookup == nil {
		lookup = os.LookupEnv
	}

	out := make([]byte, 0, len(data))
	for i := 0; i < len(data); i++ {
		b := data[i]
		if b != '$' {
			out = append(out, data[i])
			continue
		}

		// Escape sequence: $$ -> $
		if i+1 < len(data) && data[i+1] == '$' {
			out = append(out, '$')

Review comment (suggestion):
There is an added feature, which is the server default:

```
lcl:~/Documents/data_dir_1/db6$ env | grep DOLT
lcl:~/Documents/data_dir_1/db6$ cat config.yaml
listener:
  port: ${DOLT_PORT:-}

lcl:~/Documents/data_dir_1/db6$ dolt sql-server --config config.yaml
Starting server with Config HP="localhost:3306"|T="28800000"|R="false"|L="info"|S="/tmp/mysql.sock"
```

I suggest documenting this and adding a test.

Code after:
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package servercfg

import (
	"bytes"
	"fmt"
	"os"
	"regexp"
)

type envPlaceholder struct {
	varName      string
	closingBrace int
}

// interpolateEnv expands environment variable placeholders in |data|.
//
// Supported syntax:
// - ${VAR}            : expands to VAR's value; error if VAR is unset or empty
// - $$               : escapes to a literal '$'
//
// Notes:
// - Expansion is applied to the input text only (env var values are inserted literally).
func interpolateEnv(data []byte) ([]byte, error) {
	out := make([]byte, 0, len(data))
	for i := 0; i < len(data); {
		relDollar := bytes.IndexByte(data[i:], '$')
		if relDollar == -1 {
			out = append(out, data[i:]...)
			break
		}

		dollarIdx := i + relDollar
		out = append(out, data[i:dollarIdx]...)

		// Escape sequence: $$ -> $
		if dollarIdx+1 < len(data) && data[dollarIdx+1] == '$' {
			out = append(out, '$')
			i = dollarIdx + 2
			continue
		}

		// Placeholder: ${...}
		if dollarIdx+1 < len(data) && data[dollarIdx+1] == '{' {
			ph, err := parseEnvPlaceholder(data, dollarIdx)