From 7fc2b70fb3641eaf9df0f41b70559eab28c17ddc Mon Sep 17 00:00:00 2001 From: ivan-the-terrible Date: Mon, 15 Apr 2024 20:57:05 +0000 Subject: [PATCH] feat(segment): firebase segment --- src/engine/segment.go | 3 + src/segments/firebase.go | 83 ++++++++++++++++++++++ src/segments/firebase_test.go | 107 +++++++++++++++++++++++++++++ themes/cloud-context.omp.json | 9 +++ themes/schema.json | 54 ++++++--------- website/docs/segments/firebase.mdx | 44 ++++++++++++ website/sidebars.js | 1 + 7 files changed, 269 insertions(+), 32 deletions(-) create mode 100644 src/segments/firebase.go create mode 100644 src/segments/firebase_test.go create mode 100644 website/docs/segments/firebase.mdx diff --git a/src/engine/segment.go b/src/engine/segment.go index dd263b72..7be2413e 100644 --- a/src/engine/segment.go +++ b/src/engine/segment.go @@ -146,6 +146,8 @@ const ( FOSSIL SegmentType = "fossil" // GCP writes the active GCP context GCP SegmentType = "gcp" + // FIREBASE writes the active firebase project + FIREBASE SegmentType = "firebase" // GIT represents the git status and information GIT SegmentType = "git" // GITVERSION represents the gitversion information @@ -296,6 +298,7 @@ var Segments = map[SegmentType]func() SegmentWriter{ FLUTTER: func() SegmentWriter { return &segments.Flutter{} }, FOSSIL: func() SegmentWriter { return &segments.Fossil{} }, GCP: func() SegmentWriter { return &segments.Gcp{} }, + FIREBASE: func() SegmentWriter { return &segments.Firebase{} }, GIT: func() SegmentWriter { return &segments.Git{} }, GITVERSION: func() SegmentWriter { return &segments.GitVersion{} }, GOLANG: func() SegmentWriter { return &segments.Golang{} }, diff --git a/src/segments/firebase.go b/src/segments/firebase.go new file mode 100644 index 00000000..9b989d40 --- /dev/null +++ b/src/segments/firebase.go @@ -0,0 +1,83 @@ +package segments + +import ( + "encoding/json" + "errors" + "path/filepath" + + "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/properties" +) + +const ( + FIREBASENOACTIVECONFIG = "NO ACTIVE CONFIG FOUND" +) + +type Firebase struct { + props properties.Properties + env platform.Environment + + Project string +} + +type FirebaseData struct { + ActiveProject map[string]string `json:"activeProjects"` +} + +func (f *Firebase) Template() string { + return " {{ .Project}} " +} + +func (f *Firebase) Init(props properties.Properties, env platform.Environment) { + f.props = props + f.env = env +} + +func (f *Firebase) Enabled() bool { + cfgDir := filepath.Join(f.env.Home(), ".config", "configstore") + configFile, err := f.getActiveConfig(cfgDir) + if err != nil { + f.env.Error(err) + return false + } + + data, err := f.getFirebaseData(configFile) + if err != nil { + f.env.Error(err) + return false + } + + // Within the activeProjects is a key value pair + // of the path to the project and the project name + + // Test if the current directory is a project path + // and if it is, return the project name + for key, value := range data.ActiveProject { + if key == f.env.Pwd() { + f.Project = value + return true + } + } + + return false +} + +func (f *Firebase) getActiveConfig(cfgDir string) (string, error) { + activeConfigFile := filepath.Join(cfgDir, "firebase-tools.json") + activeConfigData := f.env.FileContent(activeConfigFile) + if len(activeConfigData) == 0 { + return "", errors.New(FIREBASENOACTIVECONFIG) + } + return activeConfigData, nil +} + +func (f *Firebase) getFirebaseData(configFile string) (*FirebaseData, error) { + var data FirebaseData + + err := json.Unmarshal([]byte(configFile), &data) + if err != nil { + return nil, err + } + + return &data, nil +} diff --git a/src/segments/firebase_test.go b/src/segments/firebase_test.go new file mode 100644 index 00000000..6b04097a --- /dev/null +++ b/src/segments/firebase_test.go @@ -0,0 +1,107 @@ +package segments + +import ( + "path/filepath" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/mock" + "github.com/stretchr/testify/assert" + mock2 "github.com/stretchr/testify/mock" +) + +func TestFirebaseSegment(t *testing.T) { + cases := []struct { + Case string + CfgData string + ActiveConfig string + ExpectedEnabled bool + ExpectedString string + }{ + { + Case: "happy path", + ExpectedEnabled: true, + ActiveConfig: `{ + "activeProjects": { + "path": "project-name" + } + }`, + ExpectedString: "project-name", + }, + { + Case: "no active config", + ExpectedEnabled: false, + }, + { + Case: "empty config", + ActiveConfig: "{}", + ExpectedEnabled: false, + }, + { + Case: "bad config", + ActiveConfig: "{bad}", + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := new(mock.MockedEnvironment) + env.On("Home").Return("home") + env.On("Pwd").Return("path") + fcPath := filepath.Join("home", ".config", "configstore", "firebase-tools.json") + env.On("FileContent", fcPath).Return(tc.ActiveConfig) + env.On("Error", mock2.Anything).Return() + f := Firebase{ + env: env, + } + f.Enabled() + assert.Equal(t, tc.ExpectedEnabled, f.Enabled()) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, f.Template(), f), tc.Case) + } + } +} + +func TestGetFirebaseActiveConfig(t *testing.T) { + data := + `{ + "activeProjects": { + "path": "project-name" + } + }` + cases := []struct { + Case string + ActiveConfig string + ExpectedString string + ExpectedError string + }{ + { + Case: "happy path", + ActiveConfig: data, + ExpectedString: data, + }, + { + Case: "no active config", + ActiveConfig: "", + ExpectedError: FIREBASENOACTIVECONFIG, + }, + } + + for _, tc := range cases { + env := new(mock.MockedEnvironment) + env.On("Home").Return("home") + configPath := filepath.Join("home", ".config", "configstore") + contentPath := filepath.Join(configPath, "firebase-tools.json") + env.On("FileContent", contentPath).Return(tc.ActiveConfig) + env.On("Error", mock2.Anything).Return() + f := Firebase{ + env: env, + } + got, err := f.getActiveConfig(configPath) + assert.Equal(t, tc.ExpectedString, got, tc.Case) + if len(tc.ExpectedError) > 0 { + assert.EqualError(t, err, tc.ExpectedError, tc.Case) + } else { + assert.NoError(t, err, tc.Case) + } + } +} diff --git a/themes/cloud-context.omp.json b/themes/cloud-context.omp.json index 3b7d1a55..d2bf2627 100644 --- a/themes/cloud-context.omp.json +++ b/themes/cloud-context.omp.json @@ -7,6 +7,7 @@ "cloud-text-amazon": "#4285F4", "cloud-text-azure": "#4285F4", "cloud-text-gcp": "#4285F4", + "cloud-text-firebase": "#FFA000", "error-background": "#dd0033", "error-text": "#242424", "git-text": "#238636", @@ -76,6 +77,14 @@ "template": " \ue7b2 {{ .Project }}", "type": "gcp" }, + { + "background": "p:background-color", + "foreground": "p:cloud-text-firebase", + "style": "powerline", + "powerline_symbol": "\ue0b4", + "template": " \udb82\udd67 {{ .Project }}", + "type": "firebase" + }, { "background": "p:background-color", "foreground": "p:git-text", diff --git a/themes/schema.json b/themes/schema.json index 5ffcbe07..931c2673 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -314,6 +314,7 @@ "flutter", "fossil", "gcp", + "firebase", "git", "gitversion", "go", @@ -629,11 +630,7 @@ "title": "Source", "description": "https://ohmyposh.dev/docs/segments/az#properties", "default": "first_match", - "enum": [ - "first_match", - "cli", - "pwsh" - ] + "enum": ["first_match", "cli", "pwsh"] } } } @@ -727,7 +724,7 @@ "description": "The icon representing Bazel's logo", "default": "\ue63a" }, - "extensions":{ + "extensions": { "type": "array", "title": "Extensions", "description": "The extensions to look for when determining if a folder is a Bazel workspace", @@ -747,11 +744,7 @@ "type": "array", "title": "Folders", "description": "The folders to look for when determining if a folder is a Bazel workspace", - "default": [ - "bazel-bin", - "bazel-out", - "bazel-testlogs" - ], + "default": ["bazel-bin", "bazel-out", "bazel-testlogs"], "items": { "type": "string" } @@ -890,12 +883,7 @@ "type": "string", "title": "Connection type", "description": "The connection type to display", - "enum": [ - "ethernet", - "wifi", - "cellular", - "bluetooth" - ], + "enum": ["ethernet", "wifi", "cellular", "bluetooth"], "default": "wifi|ethernet" }, "unit": { @@ -1131,9 +1119,7 @@ "type": "array", "title": "Folders", "description": "The folders to look for when determining if a folder is a Flutter workspace", - "default": [ - ".dart_tool" - ], + "default": [".dart_tool"], "items": { "type": "string" } @@ -1469,9 +1455,7 @@ "type": "array", "title": "Folders", "description": "The folders to look for when determining if a folder is a Dart workspace", - "default": [ - ".dart_tool" - ], + "default": [".dart_tool"], "items": { "type": "string" } @@ -2909,11 +2893,7 @@ "title": "units", "description": "Units of measurement. Available values are standard (kelvin), metric (celsius), and imperial (fahrenheit). Default is standard", "default": "standard", - "enum": [ - "standard", - "metric", - "imperial" - ] + "enum": ["standard", "metric", "imperial"] }, "http_timeout": { "$ref": "#/definitions/http_timeout" @@ -3185,10 +3165,7 @@ "type": "array", "title": "Extensions", "description": "The extensions to look for when determining if the current directory is an Angular project", - "default": - [ - "angular.json" - ], + "default": ["angular.json"], "items": { "type": "string" } @@ -4296,6 +4273,19 @@ "description": "https://ohmyposh.dev/docs/segments/gcp" } }, + { + "if": { + "properties": { + "type": { + "const": "firebase" + } + } + }, + "then": { + "title": "Firebase Segment", + "description": "https://ohmyposh.dev/docs/segments/firebase" + } + }, { "if": { "properties": { diff --git a/website/docs/segments/firebase.mdx b/website/docs/segments/firebase.mdx new file mode 100644 index 00000000..10f80787 --- /dev/null +++ b/website/docs/segments/firebase.mdx @@ -0,0 +1,44 @@ +--- +id: firebase +title: Firebase +sidebar_label: Firebase +--- + +## What + +Display the current active Firebase project. + +This segment leverages the `firebase-tools.json` file generated by the [Firebase CLI][firebase-cli-docs]. +The active files listed there are used to determine the current project against the current directory. + +## Sample Configuration + +import Config from '@site/src/components/Config.js'; + + + +## Template ([info][templates]) + +:::note default template + +```template +{{ .Project }} +``` + +::: + +### Properties + +| Name | Type | Description | +| ---------- | -------- | ------------------------------------------------------------------------ | +| `.Project` | `string` | the currently active project | + +[templates]: /docs/configuration/templates +[firebase-cli-docs]: https://firebase.google.com/docs/cli diff --git a/website/sidebars.js b/website/sidebars.js index 0aba4195..51ea96e6 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -77,6 +77,7 @@ module.exports = { "segments/flutter", "segments/fossil", "segments/gcp", + "segments/firebase", "segments/git", "segments/gitversion", "segments/golang",