2024-06-22 09:41:49 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2024-06-20 08:59:41 +02:00
|
|
|
package wconfig
|
|
|
|
|
|
|
|
import (
|
2024-08-28 03:49:49 +02:00
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2024-10-22 01:51:18 +02:00
|
|
|
"io/fs"
|
|
|
|
"log"
|
2024-08-28 03:49:49 +02:00
|
|
|
"os"
|
2024-06-20 08:59:41 +02:00
|
|
|
"path/filepath"
|
2024-08-28 03:49:49 +02:00
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
2024-06-20 08:59:41 +02:00
|
|
|
|
2024-09-05 23:25:45 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
|
2024-10-22 19:02:15 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
2024-09-05 23:25:45 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/wconfig/defaultconfig"
|
2024-06-20 08:59:41 +02:00
|
|
|
)
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
const SettingsFile = "settings.json"
|
2024-06-20 08:59:41 +02:00
|
|
|
|
2024-10-14 23:57:12 +02:00
|
|
|
const AnySchema = `
|
|
|
|
{
|
|
|
|
"type": "object",
|
|
|
|
"additionalProperties": true
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
2024-09-03 05:21:35 +02:00
|
|
|
type MetaSettingsType struct {
|
|
|
|
waveobj.MetaMapType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MetaSettingsType) UnmarshalJSON(data []byte) error {
|
|
|
|
var metaMap waveobj.MetaMapType
|
2024-09-24 03:51:30 +02:00
|
|
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
|
|
decoder.UseNumber()
|
|
|
|
if err := decoder.Decode(&metaMap); err != nil {
|
2024-09-03 05:21:35 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
*m = MetaSettingsType{MetaMapType: metaMap}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m MetaSettingsType) MarshalJSON() ([]byte, error) {
|
|
|
|
return json.Marshal(m.MetaMapType)
|
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
type SettingsType struct {
|
2024-10-09 22:36:02 +02:00
|
|
|
AiClear bool `json:"ai:*,omitempty"`
|
2024-10-13 00:40:14 +02:00
|
|
|
AiPreset string `json:"ai:preset,omitempty"`
|
2024-10-09 22:36:02 +02:00
|
|
|
AiApiType string `json:"ai:apitype,omitempty"`
|
|
|
|
AiBaseURL string `json:"ai:baseurl,omitempty"`
|
|
|
|
AiApiToken string `json:"ai:apitoken,omitempty"`
|
|
|
|
AiName string `json:"ai:name,omitempty"`
|
|
|
|
AiModel string `json:"ai:model,omitempty"`
|
|
|
|
AiOrgID string `json:"ai:orgid,omitempty"`
|
|
|
|
AIApiVersion string `json:"ai:apiversion,omitempty"`
|
|
|
|
AiMaxTokens float64 `json:"ai:maxtokens,omitempty"`
|
|
|
|
AiTimeoutMs float64 `json:"ai:timeoutms,omitempty"`
|
2024-09-03 05:21:35 +02:00
|
|
|
|
2024-10-01 06:19:07 +02:00
|
|
|
TermClear bool `json:"term:*,omitempty"`
|
|
|
|
TermFontSize float64 `json:"term:fontsize,omitempty"`
|
|
|
|
TermFontFamily string `json:"term:fontfamily,omitempty"`
|
2024-10-07 23:08:57 +02:00
|
|
|
TermTheme string `json:"term:theme,omitempty"`
|
2024-10-01 06:19:07 +02:00
|
|
|
TermDisableWebGl bool `json:"term:disablewebgl,omitempty"`
|
|
|
|
TermLocalShellPath string `json:"term:localshellpath,omitempty"`
|
|
|
|
TermLocalShellOpts []string `json:"term:localshellopts,omitempty"`
|
2024-10-07 18:51:23 +02:00
|
|
|
TermScrollback *int64 `json:"term:scrollback,omitempty"`
|
2024-10-14 19:05:38 +02:00
|
|
|
TermCopyOnSelect *bool `json:"term:copyonselect,omitempty"`
|
2024-06-20 08:59:41 +02:00
|
|
|
|
2024-09-05 08:08:56 +02:00
|
|
|
EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"`
|
|
|
|
EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`
|
2024-09-04 23:00:29 +02:00
|
|
|
|
2024-09-20 20:24:37 +02:00
|
|
|
WebClear bool `json:"web:*,omitempty"`
|
|
|
|
WebOpenLinksInternally bool `json:"web:openlinksinternally,omitempty"`
|
|
|
|
WebDefaultUrl string `json:"web:defaulturl,omitempty"`
|
|
|
|
WebDefaultSearch string `json:"web:defaultsearch,omitempty"`
|
2024-08-07 23:27:16 +02:00
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
BlockHeaderClear bool `json:"blockheader:*,omitempty"`
|
|
|
|
BlockHeaderShowBlockIds bool `json:"blockheader:showblockids,omitempty"`
|
2024-06-21 22:23:07 +02:00
|
|
|
|
2024-09-03 05:21:35 +02:00
|
|
|
AutoUpdateClear bool `json:"autoupdate:*,omitempty"`
|
|
|
|
AutoUpdateEnabled bool `json:"autoupdate:enabled,omitempty"`
|
|
|
|
AutoUpdateIntervalMs float64 `json:"autoupdate:intervalms,omitempty"`
|
|
|
|
AutoUpdateInstallOnQuit bool `json:"autoupdate:installonquit,omitempty"`
|
Add release channels (#385)
## New release flow
1. Run "Bump Version" workflow with the desired version bump and the
prerelease flag set to `true`. This will push a new version bump to the
target branch and create a new git tag.
- See below for more info on how the version bumping works.
2. A new "Build Helper" workflow run will kick off automatically for the
new tag. Once it is complete, test the new build locally by downloading
with the [download
script](https://github.com/wavetermdev/thenextwave/blob/main/scripts/artifacts/download-staged-artifact.sh).
3. Release the new build using the [publish
script](https://github.com/wavetermdev/thenextwave/blob/main/scripts/artifacts/publish-from-staging.sh).
This will trigger electron-updater to distribute the package to beta
users.
4. Run "Bump Version" again with a release bump (either `major`,
`minor`, or `patch`) and the prerelease flag set to `false`.
6. Release the new build to all channels using the [publish
script](https://github.com/wavetermdev/thenextwave/blob/main/scripts/artifacts/publish-from-staging.sh).
This will trigger electron-updater to distribute the package to all
users.
## Change Summary
Creates a new "Bump Version" workflow to manage versioning and tag
creation.
Build Helper is now automated.
### Version bumps
Updates the `version.cjs` script so that an argument can be passed to
trigger a version bump. Under the hood, this utilizes NPM's `semver`
package.
If arguments are present, the version will be bumped.
If only a single argument is given, the following are valid inputs:
- `none`: No-op.
- `patch`: Bumps the patch version.
- `minor`: Bumps the minor version.
- `major`: Bumps the major version.
- '1', 'true': Bumps the prerelease version.
If two arguments are given, the first argument must be either `none`,
`patch`, `minor`, or `major`. The second argument must be `1` or `true`
to bump the prerelease version.
### electron-builder
We are now using the release channels support in electron-builder. This
will automatically detect the channel being built based on the package
version to determine which channel update files need to be generated.
See
[here](https://www.electron.build/tutorials/release-using-channels.html)
for more information.
### Github Actions
#### Bump Version
This adds a new "Bump Version" workflow for managing versioning and
queuing new builds. When run, this workflow will bump the version,
create a new tag, and push the changes to the target branch. There is a
new dropdown when queuing the "Bump Version" workflow to select what
kind of version bump to perform. A bump must always be performed when
running a new build to ensure consistency.
I had to create a GitHub App to grant write permissions to our main
branch for the version bump commits. I've made a separate workflow file
to manage the version bump commits, which should help prevent tampering.
Thanks to using the GitHub API directly, I am able to make these commits
signed!
#### Build Helper
Build Helper is now triggered when new tags are created, rather than
being triggered automatically. This ensures we're always creating
artifacts from known checkpoints.
### Settings
Adds a new `autoupdate:channel` configuration to the settings file. If
unset, the default from the artifact will be used (should correspond to
the channel of the artifact when downloaded).
## Future Work
I want to add a release workflow that will automatically copy over the
corresponding version artifacts to the release bucket when a new GitHub
Release is created.
I also want to separate versions into separate subdirectories in the
release bucket so we can clean them up more-easily.
---------
Co-authored-by: wave-builder <builds@commandline.dev>
Co-authored-by: wave-builder[bot] <181805596+wave-builder[bot]@users.noreply.github.com>
2024-09-17 22:10:35 +02:00
|
|
|
AutoUpdateChannel string `json:"autoupdate:channel,omitempty"`
|
2024-07-30 07:35:21 +02:00
|
|
|
|
2024-10-07 23:08:57 +02:00
|
|
|
PreviewShowHiddenFiles *bool `json:"preview:showhiddenfiles,omitempty"`
|
|
|
|
|
2024-11-08 01:05:42 +01:00
|
|
|
WidgetClear bool `json:"widget:*,omitempty"`
|
|
|
|
WidgetShowHelp *bool `json:"widget:showhelp,omitempty"`
|
2024-06-22 09:41:49 +02:00
|
|
|
|
2024-11-18 23:41:09 +01:00
|
|
|
WindowClear bool `json:"window:*,omitempty"`
|
|
|
|
WindowTransparent bool `json:"window:transparent,omitempty"`
|
|
|
|
WindowBlur bool `json:"window:blur,omitempty"`
|
|
|
|
WindowOpacity *float64 `json:"window:opacity,omitempty"`
|
|
|
|
WindowBgColor string `json:"window:bgcolor,omitempty"`
|
|
|
|
WindowReducedMotion bool `json:"window:reducedmotion,omitempty"`
|
|
|
|
WindowTileGapSize *int64 `json:"window:tilegapsize,omitempty"`
|
|
|
|
WindowShowMenuBar bool `json:"window:showmenubar,omitempty"`
|
|
|
|
WindowNativeTitleBar bool `json:"window:nativetitlebar,omitempty"`
|
|
|
|
WindowDisableHardwareAcceleration bool `json:"window:disablehardwareacceleration,omitempty"`
|
|
|
|
WindowMaxTabCacheSize int `json:"window:maxtabcachesize,omitempty"`
|
|
|
|
WindowMagnifiedBlockOpacity *float64 `json:"window:magnifiedblockopacity,omitempty"`
|
|
|
|
WindowMagnifiedBlockSize *float64 `json:"window:magnifiedblocksize,omitempty"`
|
|
|
|
WindowMagnifiedBlockBlurPrimaryPx *int64 `json:"window:magnifiedblockblurprimarypx,omitempty"`
|
|
|
|
WindowMagnifiedBlockBlurSecondaryPx *int64 `json:"window:magnifiedblockblursecondarypx,omitempty"`
|
2024-06-25 01:25:53 +02:00
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
TelemetryClear bool `json:"telemetry:*,omitempty"`
|
|
|
|
TelemetryEnabled bool `json:"telemetry:enabled,omitempty"`
|
2024-10-11 00:50:46 +02:00
|
|
|
|
2024-10-11 02:23:27 +02:00
|
|
|
ConnClear bool `json:"conn:*,omitempty"`
|
|
|
|
ConnAskBeforeWshInstall bool `json:"conn:askbeforewshinstall,omitempty"`
|
2024-07-19 01:55:04 +02:00
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
type ConfigError struct {
|
|
|
|
File string `json:"file"`
|
|
|
|
Err string `json:"err"`
|
2024-07-25 05:34:22 +02:00
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
type FullConfigType struct {
|
2024-11-08 23:14:59 +01:00
|
|
|
Settings SettingsType `json:"settings" merge:"meta"`
|
|
|
|
MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"`
|
|
|
|
Widgets map[string]WidgetConfigType `json:"widgets"`
|
|
|
|
Presets map[string]waveobj.MetaMapType `json:"presets"`
|
|
|
|
TermThemes map[string]TermThemeType `json:"termthemes"`
|
|
|
|
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
|
2024-07-26 22:30:11 +02:00
|
|
|
}
|
|
|
|
|
2024-10-14 23:57:12 +02:00
|
|
|
func goBackWS(barr []byte, offset int) int {
|
|
|
|
if offset >= len(barr) {
|
|
|
|
offset = offset - 1
|
|
|
|
}
|
|
|
|
for i := offset - 1; i >= 0; i-- {
|
|
|
|
if barr[i] == ' ' || barr[i] == '\t' || barr[i] == '\n' || barr[i] == '\r' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func isTrailingCommaError(barr []byte, offset int) bool {
|
|
|
|
if offset >= len(barr) {
|
|
|
|
offset = offset - 1
|
|
|
|
}
|
|
|
|
offset = goBackWS(barr, offset)
|
|
|
|
if barr[offset] == '}' {
|
|
|
|
offset = goBackWS(barr, offset)
|
|
|
|
if barr[offset] == ',' {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.MetaMapType, []ConfigError) {
|
|
|
|
var cerrs []ConfigError
|
|
|
|
if readErr != nil && !os.IsNotExist(readErr) {
|
2024-10-14 23:57:12 +02:00
|
|
|
cerrs = append(cerrs, ConfigError{File: fileName, Err: readErr.Error()})
|
2024-08-28 03:49:49 +02:00
|
|
|
}
|
|
|
|
if len(barr) == 0 {
|
|
|
|
return nil, cerrs
|
|
|
|
}
|
|
|
|
var rtn waveobj.MetaMapType
|
|
|
|
err := json.Unmarshal(barr, &rtn)
|
|
|
|
if err != nil {
|
2024-10-14 23:57:12 +02:00
|
|
|
if syntaxErr, ok := err.(*json.SyntaxError); ok {
|
|
|
|
offset := syntaxErr.Offset
|
|
|
|
if offset > 0 {
|
|
|
|
offset = offset - 1
|
|
|
|
}
|
|
|
|
lineNum, colNum := utilfn.GetLineColFromOffset(barr, int(offset))
|
|
|
|
isTrailingComma := isTrailingCommaError(barr, int(offset))
|
|
|
|
if isTrailingComma {
|
|
|
|
err = fmt.Errorf("json syntax error at line %d, col %d: probably an extra trailing comma: %v", lineNum, colNum, syntaxErr)
|
|
|
|
} else {
|
|
|
|
err = fmt.Errorf("json syntax error at line %d, col %d: %v", lineNum, colNum, syntaxErr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cerrs = append(cerrs, ConfigError{File: fileName, Err: err.Error()})
|
2024-08-28 03:49:49 +02:00
|
|
|
}
|
|
|
|
return rtn, cerrs
|
2024-06-20 08:59:41 +02:00
|
|
|
}
|
|
|
|
|
2024-10-22 01:51:18 +02:00
|
|
|
func readConfigFileFS(fsys fs.FS, logPrefix string, fileName string) (waveobj.MetaMapType, []ConfigError) {
|
|
|
|
barr, readErr := fs.ReadFile(fsys, fileName)
|
2024-11-20 00:21:23 +01:00
|
|
|
if readErr != nil && strings.Contains(readErr.Error(), "invalid argument") {
|
|
|
|
// If we get an `invalid argument` error, we may be using the wrong path separator for the given FS interface. Try switching the separator.
|
|
|
|
barr, readErr = fs.ReadFile(fsys, filepath.ToSlash(fileName))
|
|
|
|
}
|
2024-10-22 01:51:18 +02:00
|
|
|
return readConfigHelper(logPrefix+fileName, barr, readErr)
|
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
func ReadDefaultsConfigFile(fileName string) (waveobj.MetaMapType, []ConfigError) {
|
2024-10-22 01:51:18 +02:00
|
|
|
return readConfigFileFS(defaultconfig.ConfigFS, "defaults:", fileName)
|
2024-07-25 05:34:22 +02:00
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
func ReadWaveHomeConfigFile(fileName string) (waveobj.MetaMapType, []ConfigError) {
|
2024-10-22 19:02:15 +02:00
|
|
|
configDirAbsPath := wavebase.GetWaveConfigDir()
|
|
|
|
configDirFsys := os.DirFS(configDirAbsPath)
|
2024-10-22 01:51:18 +02:00
|
|
|
return readConfigFileFS(configDirFsys, "", fileName)
|
2024-07-31 04:52:50 +02:00
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
func WriteWaveHomeConfigFile(fileName string, m waveobj.MetaMapType) error {
|
2024-10-22 19:02:15 +02:00
|
|
|
configDirAbsPath := wavebase.GetWaveConfigDir()
|
2024-08-28 03:49:49 +02:00
|
|
|
fullFileName := filepath.Join(configDirAbsPath, fileName)
|
|
|
|
barr, err := jsonMarshalConfigInOrder(m)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return os.WriteFile(fullFileName, barr, 0644)
|
2024-07-31 04:52:50 +02:00
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
// simple merge that overwrites
|
|
|
|
func mergeMetaMapSimple(m waveobj.MetaMapType, toMerge waveobj.MetaMapType) waveobj.MetaMapType {
|
|
|
|
if m == nil {
|
|
|
|
return toMerge
|
|
|
|
}
|
|
|
|
if toMerge == nil {
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
for k, v := range toMerge {
|
|
|
|
if v == nil {
|
|
|
|
delete(m, k)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
m[k] = v
|
|
|
|
}
|
|
|
|
if len(m) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return m
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
|
|
|
|
2024-10-22 01:51:18 +02:00
|
|
|
func mergeMetaMap(m waveobj.MetaMapType, toMerge waveobj.MetaMapType, simpleMerge bool) waveobj.MetaMapType {
|
2024-08-28 03:49:49 +02:00
|
|
|
if simpleMerge {
|
2024-10-22 01:51:18 +02:00
|
|
|
return mergeMetaMapSimple(m, toMerge)
|
2024-08-28 03:49:49 +02:00
|
|
|
} else {
|
2024-10-22 01:51:18 +02:00
|
|
|
return waveobj.MergeMeta(m, toMerge, true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectDirEntsBySuffix(dirEnts []fs.DirEntry, fileNameSuffix string) []fs.DirEntry {
|
|
|
|
var rtn []fs.DirEntry
|
|
|
|
for _, ent := range dirEnts {
|
|
|
|
if ent.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !strings.HasSuffix(ent.Name(), fileNameSuffix) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
rtn = append(rtn, ent)
|
2024-08-28 03:49:49 +02:00
|
|
|
}
|
2024-10-22 01:51:18 +02:00
|
|
|
return rtn
|
|
|
|
}
|
|
|
|
|
|
|
|
func SortFileNameDescend(files []fs.DirEntry) {
|
|
|
|
sort.Slice(files, func(i, j int) bool {
|
|
|
|
return files[i].Name() > files[j].Name()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read and merge all files in the specified directory matching the supplied suffix
|
|
|
|
func readConfigFilesForDir(fsys fs.FS, logPrefix string, dirName string, fileName string, simpleMerge bool) (waveobj.MetaMapType, []ConfigError) {
|
|
|
|
dirEnts, _ := fs.ReadDir(fsys, dirName)
|
|
|
|
suffixEnts := selectDirEntsBySuffix(dirEnts, fileName+".json")
|
|
|
|
SortFileNameDescend(suffixEnts)
|
|
|
|
var rtn waveobj.MetaMapType
|
|
|
|
var errs []ConfigError
|
|
|
|
for _, ent := range suffixEnts {
|
|
|
|
fileVal, cerrs := readConfigFileFS(fsys, logPrefix, filepath.Join(dirName, ent.Name()))
|
|
|
|
rtn = mergeMetaMap(rtn, fileVal, simpleMerge)
|
|
|
|
errs = append(errs, cerrs...)
|
|
|
|
}
|
|
|
|
return rtn, errs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read and merge all files in the specified config filesystem matching the patterns `<partName>.json` and `<partName>/*.json`
|
|
|
|
func readConfigPartForFS(fsys fs.FS, logPrefix string, partName string, simpleMerge bool) (waveobj.MetaMapType, []ConfigError) {
|
|
|
|
config, errs := readConfigFilesForDir(fsys, logPrefix, partName, "", simpleMerge)
|
|
|
|
allErrs := errs
|
|
|
|
rtn := config
|
|
|
|
config, errs = readConfigFileFS(fsys, logPrefix, partName+".json")
|
|
|
|
allErrs = append(allErrs, errs...)
|
|
|
|
return mergeMetaMap(rtn, config, simpleMerge), allErrs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Combine files from the defaults and home directory for the specified config part name
|
|
|
|
func readConfigPart(partName string, simpleMerge bool) (waveobj.MetaMapType, []ConfigError) {
|
2024-10-22 19:02:15 +02:00
|
|
|
configDirAbsPath := wavebase.GetWaveConfigDir()
|
|
|
|
configDirFsys := os.DirFS(configDirAbsPath)
|
2024-10-22 01:51:18 +02:00
|
|
|
defaultConfigs, cerrs := readConfigPartForFS(defaultconfig.ConfigFS, "defaults:", partName, simpleMerge)
|
|
|
|
homeConfigs, cerrs1 := readConfigPartForFS(configDirFsys, "", partName, simpleMerge)
|
|
|
|
|
|
|
|
rtn := defaultConfigs
|
|
|
|
allErrs := append(cerrs, cerrs1...)
|
|
|
|
return mergeMetaMap(rtn, homeConfigs, simpleMerge), allErrs
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
func ReadFullConfig() FullConfigType {
|
|
|
|
var fullConfig FullConfigType
|
|
|
|
configRType := reflect.TypeOf(fullConfig)
|
|
|
|
configRVal := reflect.ValueOf(&fullConfig).Elem()
|
|
|
|
for fieldIdx := 0; fieldIdx < configRType.NumField(); fieldIdx++ {
|
|
|
|
field := configRType.Field(fieldIdx)
|
|
|
|
if field.PkgPath != "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
configFile := field.Tag.Get("configfile")
|
|
|
|
if configFile == "-" {
|
|
|
|
continue
|
|
|
|
}
|
2024-08-28 07:02:21 +02:00
|
|
|
jsonTag := utilfn.GetJsonTag(field)
|
2024-10-22 01:51:18 +02:00
|
|
|
simpleMerge := field.Tag.Get("merge") == ""
|
|
|
|
var configPart waveobj.MetaMapType
|
|
|
|
var errs []ConfigError
|
2024-08-28 03:49:49 +02:00
|
|
|
if jsonTag == "-" || jsonTag == "" {
|
|
|
|
continue
|
2024-10-22 01:51:18 +02:00
|
|
|
} else {
|
|
|
|
configPart, errs = readConfigPart(jsonTag, simpleMerge)
|
2024-08-28 03:49:49 +02:00
|
|
|
}
|
2024-10-22 01:51:18 +02:00
|
|
|
fullConfig.ConfigErrors = append(fullConfig.ConfigErrors, errs...)
|
2024-08-28 03:49:49 +02:00
|
|
|
if configPart != nil {
|
|
|
|
fieldPtr := configRVal.Field(fieldIdx).Addr().Interface()
|
|
|
|
utilfn.ReUnmarshal(fieldPtr, configPart)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fullConfig
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
|
|
|
|
2024-10-22 01:51:18 +02:00
|
|
|
func GetConfigSubdirs() []string {
|
|
|
|
var fullConfig FullConfigType
|
|
|
|
configRType := reflect.TypeOf(fullConfig)
|
|
|
|
var retVal []string
|
2024-10-22 19:02:15 +02:00
|
|
|
configDirAbsPath := wavebase.GetWaveConfigDir()
|
2024-10-22 01:51:18 +02:00
|
|
|
for fieldIdx := 0; fieldIdx < configRType.NumField(); fieldIdx++ {
|
|
|
|
field := configRType.Field(fieldIdx)
|
|
|
|
if field.PkgPath != "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
configFile := field.Tag.Get("configfile")
|
|
|
|
if configFile == "-" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
jsonTag := utilfn.GetJsonTag(field)
|
|
|
|
if jsonTag != "-" && jsonTag != "" && jsonTag != "settings" {
|
|
|
|
retVal = append(retVal, filepath.Join(configDirAbsPath, jsonTag))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Printf("subdirs: %v\n", retVal)
|
|
|
|
return retVal
|
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
func getConfigKeyType(configKey string) reflect.Type {
|
|
|
|
ctype := reflect.TypeOf(SettingsType{})
|
|
|
|
for i := 0; i < ctype.NumField(); i++ {
|
|
|
|
field := ctype.Field(i)
|
2024-08-28 07:02:21 +02:00
|
|
|
jsonTag := utilfn.GetJsonTag(field)
|
|
|
|
if jsonTag == configKey {
|
2024-08-28 03:49:49 +02:00
|
|
|
return field.Type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
func getConfigKeyNamespace(key string) string {
|
|
|
|
colonIdx := strings.Index(key, ":")
|
|
|
|
if colonIdx == -1 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return key[:colonIdx]
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
|
|
|
|
2024-08-28 03:49:49 +02:00
|
|
|
func orderConfigKeys(m waveobj.MetaMapType) []string {
|
|
|
|
keys := make([]string, 0, len(m))
|
|
|
|
for k := range m {
|
|
|
|
keys = append(keys, k)
|
2024-07-25 05:34:22 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
|
|
k1 := keys[i]
|
|
|
|
k2 := keys[j]
|
|
|
|
k1ns := getConfigKeyNamespace(k1)
|
|
|
|
k2ns := getConfigKeyNamespace(k2)
|
|
|
|
if k1ns != k2ns {
|
|
|
|
return k1ns < k2ns
|
2024-07-25 05:34:22 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
return k1 < k2
|
|
|
|
})
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
|
|
|
func reindentJson(barr []byte, indentStr string) []byte {
|
|
|
|
if len(barr) < 2 {
|
|
|
|
return barr
|
2024-07-25 05:34:22 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
if barr[0] != '{' && barr[0] != '[' {
|
|
|
|
return barr
|
2024-07-25 05:34:22 +02:00
|
|
|
}
|
2024-09-24 03:51:30 +02:00
|
|
|
if bytes.Contains(barr, []byte("\n")) {
|
2024-08-28 03:49:49 +02:00
|
|
|
return barr
|
2024-07-31 04:52:50 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
outputLines := bytes.Split(barr, []byte("\n"))
|
|
|
|
for i, line := range outputLines {
|
|
|
|
if i == 0 || i == len(outputLines)-1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
outputLines[i] = append([]byte(indentStr), line...)
|
2024-07-31 04:52:50 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
return bytes.Join(outputLines, []byte("\n"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func jsonMarshalConfigInOrder(m waveobj.MetaMapType) ([]byte, error) {
|
|
|
|
if len(m) == 0 {
|
|
|
|
return []byte("{}"), nil
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
var buf bytes.Buffer
|
|
|
|
orderedKeys := orderConfigKeys(m)
|
|
|
|
buf.WriteString("{\n")
|
|
|
|
for idx, key := range orderedKeys {
|
|
|
|
val := m[key]
|
|
|
|
keyBarr, err := json.Marshal(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
valBarr, err := json.MarshalIndent(val, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
valBarr = reindentJson(valBarr, " ")
|
|
|
|
buf.WriteString(" ")
|
|
|
|
buf.Write(keyBarr)
|
|
|
|
buf.WriteString(": ")
|
|
|
|
buf.Write(valBarr)
|
|
|
|
if idx < len(orderedKeys)-1 {
|
|
|
|
buf.WriteString(",")
|
|
|
|
}
|
|
|
|
buf.WriteString("\n")
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
buf.WriteString("}")
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
2024-09-24 03:51:30 +02:00
|
|
|
var dummyNumber json.Number
|
|
|
|
|
|
|
|
func convertJsonNumber(num json.Number, ctype reflect.Type) (interface{}, error) {
|
|
|
|
// ctype might be int64, float64, string, *int64, *float64, *string
|
|
|
|
// switch on ctype first
|
|
|
|
if ctype.Kind() == reflect.Pointer {
|
|
|
|
ctype = ctype.Elem()
|
|
|
|
}
|
|
|
|
if reflect.Int64 == ctype.Kind() {
|
|
|
|
if ival, err := num.Int64(); err == nil {
|
|
|
|
return ival, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid number for int64: %s", num)
|
|
|
|
}
|
|
|
|
if reflect.Float64 == ctype.Kind() {
|
|
|
|
if fval, err := num.Float64(); err == nil {
|
|
|
|
return fval, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid number for float64: %s", num)
|
|
|
|
}
|
|
|
|
if reflect.String == ctype.Kind() {
|
|
|
|
return num.String(), nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("cannot convert number to %s", ctype)
|
|
|
|
}
|
|
|
|
|
2024-08-28 07:02:21 +02:00
|
|
|
func SetBaseConfigValue(toMerge waveobj.MetaMapType) error {
|
2024-08-28 03:49:49 +02:00
|
|
|
m, cerrs := ReadWaveHomeConfigFile(SettingsFile)
|
|
|
|
if len(cerrs) > 0 {
|
|
|
|
return fmt.Errorf("error reading config file: %v", cerrs[0])
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
if m == nil {
|
|
|
|
m = make(waveobj.MetaMapType)
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
2024-08-28 07:02:21 +02:00
|
|
|
for configKey, val := range toMerge {
|
|
|
|
ctype := getConfigKeyType(configKey)
|
|
|
|
if ctype == nil {
|
|
|
|
return fmt.Errorf("invalid config key: %s", configKey)
|
|
|
|
}
|
|
|
|
if val == nil {
|
|
|
|
delete(m, configKey)
|
|
|
|
} else {
|
2024-09-24 03:51:30 +02:00
|
|
|
rtype := reflect.TypeOf(val)
|
|
|
|
if rtype == reflect.TypeOf(dummyNumber) {
|
|
|
|
convertedVal, err := convertJsonNumber(val.(json.Number), ctype)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot convert %s: %v", configKey, err)
|
|
|
|
}
|
|
|
|
val = convertedVal
|
|
|
|
rtype = reflect.TypeOf(val)
|
|
|
|
}
|
|
|
|
if rtype != ctype {
|
|
|
|
if ctype == reflect.PointerTo(rtype) {
|
|
|
|
m[configKey] = &val
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("invalid value type for %s: %T", configKey, val)
|
|
|
|
}
|
2024-08-28 07:02:21 +02:00
|
|
|
}
|
|
|
|
m[configKey] = val
|
2024-08-28 03:49:49 +02:00
|
|
|
}
|
2024-07-31 08:22:41 +02:00
|
|
|
}
|
2024-08-28 03:49:49 +02:00
|
|
|
return WriteWaveHomeConfigFile(SettingsFile, m)
|
|
|
|
}
|
|
|
|
|
|
|
|
type WidgetConfigType struct {
|
|
|
|
DisplayOrder float64 `json:"display:order,omitempty"`
|
|
|
|
Icon string `json:"icon,omitempty"`
|
|
|
|
Color string `json:"color,omitempty"`
|
|
|
|
Label string `json:"label,omitempty"`
|
|
|
|
Description string `json:"description,omitempty"`
|
|
|
|
BlockDef waveobj.BlockDef `json:"blockdef"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type MimeTypeConfigType struct {
|
|
|
|
Icon string `json:"icon"`
|
|
|
|
Color string `json:"color"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type TermThemeType struct {
|
2024-08-30 06:35:22 +02:00
|
|
|
DisplayName string `json:"display:name"`
|
|
|
|
DisplayOrder float64 `json:"display:order"`
|
|
|
|
Black string `json:"black"`
|
|
|
|
Red string `json:"red"`
|
|
|
|
Green string `json:"green"`
|
|
|
|
Yellow string `json:"yellow"`
|
|
|
|
Blue string `json:"blue"`
|
|
|
|
Magenta string `json:"magenta"`
|
|
|
|
Cyan string `json:"cyan"`
|
|
|
|
White string `json:"white"`
|
|
|
|
BrightBlack string `json:"brightBlack"`
|
|
|
|
BrightRed string `json:"brightRed"`
|
|
|
|
BrightGreen string `json:"brightGreen"`
|
|
|
|
BrightYellow string `json:"brightYellow"`
|
|
|
|
BrightBlue string `json:"brightBlue"`
|
|
|
|
BrightMagenta string `json:"brightMagenta"`
|
|
|
|
BrightCyan string `json:"brightCyan"`
|
|
|
|
BrightWhite string `json:"brightWhite"`
|
|
|
|
Gray string `json:"gray"`
|
|
|
|
CmdText string `json:"cmdtext"`
|
|
|
|
Foreground string `json:"foreground"`
|
|
|
|
SelectionBackground string `json:"selectionBackground"`
|
|
|
|
Background string `json:"background"`
|
2024-10-06 21:42:25 +02:00
|
|
|
Cursor string `json:"cursor"`
|
2024-06-20 08:59:41 +02:00
|
|
|
}
|