From 005b2b198c78e609d6772021d892017164ad217b Mon Sep 17 00:00:00 2001 From: D E Costa Date: Fri, 24 Nov 2023 17:56:05 -0500 Subject: [PATCH 1/7] Add Exercism plugin --- plugins/exercism/api_key.go | 94 ++++++++++++++++++++++++ plugins/exercism/api_key_test.go | 48 ++++++++++++ plugins/exercism/exercism.go | 25 +++++++ plugins/exercism/plugin.go | 22 ++++++ plugins/exercism/test-fixtures/user.json | 5 ++ 5 files changed, 194 insertions(+) create mode 100644 plugins/exercism/api_key.go create mode 100644 plugins/exercism/api_key_test.go create mode 100644 plugins/exercism/exercism.go create mode 100644 plugins/exercism/plugin.go create mode 100644 plugins/exercism/test-fixtures/user.json diff --git a/plugins/exercism/api_key.go b/plugins/exercism/api_key.go new file mode 100644 index 000000000..98551bcc0 --- /dev/null +++ b/plugins/exercism/api_key.go @@ -0,0 +1,94 @@ +package exercism + +import ( + "context" + "encoding/json" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/importer" + "github.com/1Password/shell-plugins/sdk/provision" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func APIKey() schema.CredentialType { + return schema.CredentialType{ + Name: credname.APIKey, + DocsURL: sdk.URL("https://exercism.org/docs/using/solving-exercises/working-locally"), + ManagementURL: sdk.URL("https://exercism.org/settings/api_cli"), + Fields: []schema.CredentialField{ + { + Name: fieldname.URL, + MarkdownDescription: `The URL of the Exercism API.`, + Secret: false, + }, + { + Name: fieldname.APIKey, + MarkdownDescription: "API Key used to authenticate to Exercism.", + Secret: true, + Composition: &schema.ValueComposition{ + Length: 37, + Charset: schema.Charset{ + Lowercase: true, + Digits: true, + }, + }, + }, + { + Name: sdk.FieldName("Directory"), + MarkdownDescription: `The path to the workspace directory where the exercises are stored.`, + Secret: false, + }, + }, + DefaultProvisioner: provision.TempFile( + tempFileConfig, + provision.AtFixedPath("~/.config/exercism/user.json"), + ), + Importer: importer.TryAll( + TryExercismConfigFile(), + )} +} + +func TryExercismConfigFile() sdk.Importer { + return importer.TryFile("~/.config/exercism/user.json", func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) { + var config Config + if err := contents.ToJSON(&config); err != nil { + out.AddError(err) + return + } + + if config.URL == "" || config.APIKey == "" || config.Workspace == "" { + return + } + + out.AddCandidate(sdk.ImportCandidate{ + Fields: map[sdk.FieldName]string{ + fieldname.APIKey: config.APIKey, + fieldname.URL: config.URL, + sdk.FieldName("Directory"): config.Workspace, + }, + }) + }) +} + +type Config struct { + URL string `json:"apibaseurl"` + APIKey string `json:"token"` + Workspace string `json:"workspace"` +} + +func tempFileConfig(in sdk.ProvisionInput) ([]byte, error) { + config := Config{ + URL: in.ItemFields[fieldname.URL], + APIKey: in.ItemFields[fieldname.APIKey], + Workspace: in.ItemFields[sdk.FieldName("Directory")], + } + + contents, err := json.Marshal(&config) + if err != nil { + return nil, err + } + + return []byte(contents), nil +} diff --git a/plugins/exercism/api_key_test.go b/plugins/exercism/api_key_test.go new file mode 100644 index 000000000..a136ad901 --- /dev/null +++ b/plugins/exercism/api_key_test.go @@ -0,0 +1,48 @@ +package exercism + +import ( + "strings" + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestAPIKeyProvisioner(t *testing.T) { + plugintest.TestProvisioner(t, APIKey().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "default": { + ItemFields: map[sdk.FieldName]string{ + fieldname.URL: "https://api.exercism.io/v1", + fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", + sdk.FieldName("Directory"): "/Users/username/exercism", + }, + ExpectedOutput: sdk.ProvisionOutput{ + Files: map[string]sdk.OutputFile{ + "~/.config/exercism/user.json": { + Contents: []byte(strings.Join(strings.Fields(plugintest.LoadFixture(t, "user.json")), "")), + }, + }, + }, + }, + }) +} + +func TestAPIKeyImporter(t *testing.T) { + plugintest.TestImporter(t, APIKey().Importer, map[string]plugintest.ImportCase{ + "config file": { + Files: map[string]string{ + "~/.config/exercism/user.json": plugintest.LoadFixture(t, "user.json"), + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.URL: "https://api.exercism.io/v1", + fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", + sdk.FieldName("Directory"): "/Users/username/exercism", + }, + }, + }, + }, + }) +} diff --git a/plugins/exercism/exercism.go b/plugins/exercism/exercism.go new file mode 100644 index 000000000..9f4995e6e --- /dev/null +++ b/plugins/exercism/exercism.go @@ -0,0 +1,25 @@ +package exercism + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" +) + +func ExercismCLI() schema.Executable { + return schema.Executable{ + Name: "Exercism CLI", + Runs: []string{"exercism"}, + DocsURL: sdk.URL("https://exercism.org/docs/using/solving-exercises/working-locally"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + { + Name: credname.APIKey, + }, + }, + } +} diff --git a/plugins/exercism/plugin.go b/plugins/exercism/plugin.go new file mode 100644 index 000000000..ec73a0b1d --- /dev/null +++ b/plugins/exercism/plugin.go @@ -0,0 +1,22 @@ +package exercism + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "exercism", + Platform: schema.PlatformInfo{ + Name: "Exercism", + Homepage: sdk.URL("https://exercism.org"), + }, + Credentials: []schema.CredentialType{ + APIKey(), + }, + Executables: []schema.Executable{ + ExercismCLI(), + }, + } +} diff --git a/plugins/exercism/test-fixtures/user.json b/plugins/exercism/test-fixtures/user.json new file mode 100644 index 000000000..de7b91c17 --- /dev/null +++ b/plugins/exercism/test-fixtures/user.json @@ -0,0 +1,5 @@ +{ + "apibaseurl":"https://api.exercism.io/v1", + "token":"v1o2p80wuf2qhnurrvf8rigro6sp38example", + "workspace":"/Users/username/exercism" +} From 917fdfee059a64ec8aa7dbea05078c6fd5e2e71b Mon Sep 17 00:00:00 2001 From: D E Costa Date: Fri, 24 Nov 2023 18:05:37 -0500 Subject: [PATCH 2/7] run go fmt --- plugins/exercism/api_key.go | 20 ++++++++++---------- plugins/exercism/api_key_test.go | 22 +++++++++++----------- plugins/exercism/exercism.go | 6 +++--- plugins/exercism/plugin.go | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/plugins/exercism/api_key.go b/plugins/exercism/api_key.go index 98551bcc0..857f69c06 100644 --- a/plugins/exercism/api_key.go +++ b/plugins/exercism/api_key.go @@ -19,9 +19,9 @@ func APIKey() schema.CredentialType { ManagementURL: sdk.URL("https://exercism.org/settings/api_cli"), Fields: []schema.CredentialField{ { - Name: fieldname.URL, + Name: fieldname.URL, MarkdownDescription: `The URL of the Exercism API.`, - Secret: false, + Secret: false, }, { Name: fieldname.APIKey, @@ -36,9 +36,9 @@ func APIKey() schema.CredentialType { }, }, { - Name: sdk.FieldName("Directory"), + Name: sdk.FieldName("Directory"), MarkdownDescription: `The path to the workspace directory where the exercises are stored.`, - Secret: false, + Secret: false, }, }, DefaultProvisioner: provision.TempFile( @@ -64,8 +64,8 @@ func TryExercismConfigFile() sdk.Importer { out.AddCandidate(sdk.ImportCandidate{ Fields: map[sdk.FieldName]string{ - fieldname.APIKey: config.APIKey, - fieldname.URL: config.URL, + fieldname.APIKey: config.APIKey, + fieldname.URL: config.URL, sdk.FieldName("Directory"): config.Workspace, }, }) @@ -73,15 +73,15 @@ func TryExercismConfigFile() sdk.Importer { } type Config struct { - URL string `json:"apibaseurl"` - APIKey string `json:"token"` + URL string `json:"apibaseurl"` + APIKey string `json:"token"` Workspace string `json:"workspace"` } func tempFileConfig(in sdk.ProvisionInput) ([]byte, error) { config := Config{ - URL: in.ItemFields[fieldname.URL], - APIKey: in.ItemFields[fieldname.APIKey], + URL: in.ItemFields[fieldname.URL], + APIKey: in.ItemFields[fieldname.APIKey], Workspace: in.ItemFields[sdk.FieldName("Directory")], } diff --git a/plugins/exercism/api_key_test.go b/plugins/exercism/api_key_test.go index a136ad901..ba20a1fe2 100644 --- a/plugins/exercism/api_key_test.go +++ b/plugins/exercism/api_key_test.go @@ -3,18 +3,18 @@ package exercism import ( "strings" "testing" - + "github.com/1Password/shell-plugins/sdk" "github.com/1Password/shell-plugins/sdk/plugintest" "github.com/1Password/shell-plugins/sdk/schema/fieldname" ) - + func TestAPIKeyProvisioner(t *testing.T) { plugintest.TestProvisioner(t, APIKey().DefaultProvisioner, map[string]plugintest.ProvisionCase{ "default": { - ItemFields: map[sdk.FieldName]string{ - fieldname.URL: "https://api.exercism.io/v1", - fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", + ItemFields: map[sdk.FieldName]string{ + fieldname.URL: "https://api.exercism.io/v1", + fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", sdk.FieldName("Directory"): "/Users/username/exercism", }, ExpectedOutput: sdk.ProvisionOutput{ @@ -35,13 +35,13 @@ func TestAPIKeyImporter(t *testing.T) { "~/.config/exercism/user.json": plugintest.LoadFixture(t, "user.json"), }, ExpectedCandidates: []sdk.ImportCandidate{ - { - Fields: map[sdk.FieldName]string{ - fieldname.URL: "https://api.exercism.io/v1", - fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", + { + Fields: map[sdk.FieldName]string{ + fieldname.URL: "https://api.exercism.io/v1", + fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", sdk.FieldName("Directory"): "/Users/username/exercism", - }, - }, + }, + }, }, }, }) diff --git a/plugins/exercism/exercism.go b/plugins/exercism/exercism.go index 9f4995e6e..28e5dbf89 100644 --- a/plugins/exercism/exercism.go +++ b/plugins/exercism/exercism.go @@ -9,9 +9,9 @@ import ( func ExercismCLI() schema.Executable { return schema.Executable{ - Name: "Exercism CLI", - Runs: []string{"exercism"}, - DocsURL: sdk.URL("https://exercism.org/docs/using/solving-exercises/working-locally"), + Name: "Exercism CLI", + Runs: []string{"exercism"}, + DocsURL: sdk.URL("https://exercism.org/docs/using/solving-exercises/working-locally"), NeedsAuth: needsauth.IfAll( needsauth.NotForHelpOrVersion(), needsauth.NotWithoutArgs(), diff --git a/plugins/exercism/plugin.go b/plugins/exercism/plugin.go index ec73a0b1d..0ad04eb6d 100644 --- a/plugins/exercism/plugin.go +++ b/plugins/exercism/plugin.go @@ -10,7 +10,7 @@ func New() schema.Plugin { Name: "exercism", Platform: schema.PlatformInfo{ Name: "Exercism", - Homepage: sdk.URL("https://exercism.org"), + Homepage: sdk.URL("https://exercism.org"), }, Credentials: []schema.CredentialType{ APIKey(), From bda4e18c4f05064a17d06bafe0c20c68414bdd4c Mon Sep 17 00:00:00 2001 From: Scott Lougheed Date: Fri, 22 May 2026 10:37:13 -0700 Subject: [PATCH 3/7] fixing token schema and test --- plugins/exercism/api_key.go | 3 ++- plugins/exercism/api_key_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/exercism/api_key.go b/plugins/exercism/api_key.go index 857f69c06..c492bef5d 100644 --- a/plugins/exercism/api_key.go +++ b/plugins/exercism/api_key.go @@ -28,10 +28,11 @@ func APIKey() schema.CredentialType { MarkdownDescription: "API Key used to authenticate to Exercism.", Secret: true, Composition: &schema.ValueComposition{ - Length: 37, + Length: 36, Charset: schema.Charset{ Lowercase: true, Digits: true, + Specific: []rune{'-'}, }, }, }, diff --git a/plugins/exercism/api_key_test.go b/plugins/exercism/api_key_test.go index ba20a1fe2..061fecba6 100644 --- a/plugins/exercism/api_key_test.go +++ b/plugins/exercism/api_key_test.go @@ -14,7 +14,7 @@ func TestAPIKeyProvisioner(t *testing.T) { "default": { ItemFields: map[sdk.FieldName]string{ fieldname.URL: "https://api.exercism.io/v1", - fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", + fieldname.APIKey: "1ge7a536-9d1c-4a26-9ww3-3d423example", sdk.FieldName("Directory"): "/Users/username/exercism", }, ExpectedOutput: sdk.ProvisionOutput{ From fdf82fa29d6d5a07f65e9f4a43dc9f32bed9284c Mon Sep 17 00:00:00 2001 From: Scott Lougheed Date: Fri, 22 May 2026 10:41:46 -0700 Subject: [PATCH 4/7] updating outdated baseURL --- plugins/exercism/api_key_test.go | 4 ++-- plugins/exercism/test-fixtures/user.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/exercism/api_key_test.go b/plugins/exercism/api_key_test.go index 061fecba6..9b65e0298 100644 --- a/plugins/exercism/api_key_test.go +++ b/plugins/exercism/api_key_test.go @@ -13,7 +13,7 @@ func TestAPIKeyProvisioner(t *testing.T) { plugintest.TestProvisioner(t, APIKey().DefaultProvisioner, map[string]plugintest.ProvisionCase{ "default": { ItemFields: map[sdk.FieldName]string{ - fieldname.URL: "https://api.exercism.io/v1", + fieldname.URL: "https://api.exercism.org/v1", fieldname.APIKey: "1ge7a536-9d1c-4a26-9ww3-3d423example", sdk.FieldName("Directory"): "/Users/username/exercism", }, @@ -37,7 +37,7 @@ func TestAPIKeyImporter(t *testing.T) { ExpectedCandidates: []sdk.ImportCandidate{ { Fields: map[sdk.FieldName]string{ - fieldname.URL: "https://api.exercism.io/v1", + fieldname.URL: "https://api.exercism.org/v1", fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", sdk.FieldName("Directory"): "/Users/username/exercism", }, diff --git a/plugins/exercism/test-fixtures/user.json b/plugins/exercism/test-fixtures/user.json index de7b91c17..742444df1 100644 --- a/plugins/exercism/test-fixtures/user.json +++ b/plugins/exercism/test-fixtures/user.json @@ -1,5 +1,5 @@ { - "apibaseurl":"https://api.exercism.io/v1", + "apibaseurl":"https://api.exercism.org/v1", "token":"v1o2p80wuf2qhnurrvf8rigro6sp38example", "workspace":"/Users/username/exercism" } From 7a049d6599f55f03b73be682fbc76a1804e7d2b4 Mon Sep 17 00:00:00 2001 From: Scott Lougheed Date: Fri, 22 May 2026 11:01:14 -0700 Subject: [PATCH 5/7] adding additional commands to auth exclusion --- plugins/exercism/exercism.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/exercism/exercism.go b/plugins/exercism/exercism.go index 28e5dbf89..b82a799db 100644 --- a/plugins/exercism/exercism.go +++ b/plugins/exercism/exercism.go @@ -15,6 +15,7 @@ func ExercismCLI() schema.Executable { NeedsAuth: needsauth.IfAll( needsauth.NotForHelpOrVersion(), needsauth.NotWithoutArgs(), + needsauth.NotForExactArgs("completion", "upgrade", "workspace", "troubleshoot"), ), Uses: []schema.CredentialUsage{ { From 92ff1e57a0fdf8a59d6840e376dfd7141ccde8ad Mon Sep 17 00:00:00 2001 From: Scott Lougheed Date: Fri, 22 May 2026 11:42:21 -0700 Subject: [PATCH 6/7] fixing example key in importer --- plugins/exercism/api_key.go | 2 +- plugins/exercism/api_key_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/exercism/api_key.go b/plugins/exercism/api_key.go index c492bef5d..7b333c270 100644 --- a/plugins/exercism/api_key.go +++ b/plugins/exercism/api_key.go @@ -32,7 +32,7 @@ func APIKey() schema.CredentialType { Charset: schema.Charset{ Lowercase: true, Digits: true, - Specific: []rune{'-'}, + Specific: []rune{'-'}, }, }, }, diff --git a/plugins/exercism/api_key_test.go b/plugins/exercism/api_key_test.go index 9b65e0298..34cbb2acb 100644 --- a/plugins/exercism/api_key_test.go +++ b/plugins/exercism/api_key_test.go @@ -38,7 +38,7 @@ func TestAPIKeyImporter(t *testing.T) { { Fields: map[sdk.FieldName]string{ fieldname.URL: "https://api.exercism.org/v1", - fieldname.APIKey: "v1o2p80wuf2qhnurrvf8rigro6sp38example", + fieldname.APIKey: "1ge7a536-9d1c-4a26-9ww3-3d423example", sdk.FieldName("Directory"): "/Users/username/exercism", }, }, From 716477e4e0ba1b9521083bd823e43af3ab906343 Mon Sep 17 00:00:00 2001 From: Scott Lougheed Date: Fri, 22 May 2026 12:44:10 -0700 Subject: [PATCH 7/7] fixing example key --- plugins/exercism/test-fixtures/user.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/exercism/test-fixtures/user.json b/plugins/exercism/test-fixtures/user.json index 742444df1..2f8e7bada 100644 --- a/plugins/exercism/test-fixtures/user.json +++ b/plugins/exercism/test-fixtures/user.json @@ -1,5 +1,5 @@ { "apibaseurl":"https://api.exercism.org/v1", - "token":"v1o2p80wuf2qhnurrvf8rigro6sp38example", + "token":"1ge7a536-9d1c-4a26-9ww3-3d423example", "workspace":"/Users/username/exercism" }