diff --git a/plugins/exercism/api_key.go b/plugins/exercism/api_key.go new file mode 100644 index 000000000..7b333c270 --- /dev/null +++ b/plugins/exercism/api_key.go @@ -0,0 +1,95 @@ +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: 36, + Charset: schema.Charset{ + Lowercase: true, + Digits: true, + Specific: []rune{'-'}, + }, + }, + }, + { + 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..34cbb2acb --- /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.org/v1", + fieldname.APIKey: "1ge7a536-9d1c-4a26-9ww3-3d423example", + 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.org/v1", + fieldname.APIKey: "1ge7a536-9d1c-4a26-9ww3-3d423example", + sdk.FieldName("Directory"): "/Users/username/exercism", + }, + }, + }, + }, + }) +} diff --git a/plugins/exercism/exercism.go b/plugins/exercism/exercism.go new file mode 100644 index 000000000..b82a799db --- /dev/null +++ b/plugins/exercism/exercism.go @@ -0,0 +1,26 @@ +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(), + needsauth.NotForExactArgs("completion", "upgrade", "workspace", "troubleshoot"), + ), + Uses: []schema.CredentialUsage{ + { + Name: credname.APIKey, + }, + }, + } +} diff --git a/plugins/exercism/plugin.go b/plugins/exercism/plugin.go new file mode 100644 index 000000000..0ad04eb6d --- /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..2f8e7bada --- /dev/null +++ b/plugins/exercism/test-fixtures/user.json @@ -0,0 +1,5 @@ +{ + "apibaseurl":"https://api.exercism.org/v1", + "token":"1ge7a536-9d1c-4a26-9ww3-3d423example", + "workspace":"/Users/username/exercism" +}