Skip to content

Commit

Permalink
Update godoc
Browse files Browse the repository at this point in the history
  • Loading branch information
oklahomer committed Dec 30, 2021
1 parent af6e976 commit 52e4872
Show file tree
Hide file tree
Showing 43 changed files with 838 additions and 731 deletions.
26 changes: 12 additions & 14 deletions _examples/simple/main.go
@@ -1,6 +1,4 @@
/*
Package main provides a simple bot experience using slack.Adapter with multiple plugin commands and scheduled tasks.
*/
// Package main provides a simple bot experience using slack.Adapter with multiple plugin commands and scheduled tasks.
package main

import (
Expand Down Expand Up @@ -35,7 +33,7 @@ type myConfig struct {
}

func newMyConfig() *myConfig {
// Use constructor for each config struct, so default values are pre-set.
// Use a constructor function for each config struct, so default values are pre-set.
return &myConfig{
CacheConfig: sarah.NewCacheConfig(),
Slack: slack.NewConfig(),
Expand All @@ -51,37 +49,37 @@ func main() {
panic("./bin/examples -config=/path/to/config/app.yml")
}

// Read configuration file.
// Read a configuration file.
config := readConfig(*path)

// When Bot encounters critical states, send alert to LINE.
// Any number of Alerter implementation can be registered.
// When the Bot encounters critical states, send an alert to LINE.
// Any number of Alerter implementations can be registered.
sarah.RegisterAlerter(line.New(config.LineAlerter))

// Setup storage that can be shared among different Bot implementation.
// Set up a storage that can be shared among different Bot implementations.
storage := sarah.NewUserContextStorage(config.CacheConfig)

// Setup Slack Bot.
// Set up Slack Bot.
setupSlack(config.Slack, storage)

// Setup some commands.
// Set up some commands.
todoCmd := todo.BuildCommand(&todo.DummyStorage{})
sarah.RegisterCommand(slack.SLACK, todoCmd)

// Directly add Command to Bot.
// This Command is not subject to config file supervision.
sarah.RegisterCommand(slack.SLACK, echo.Command)

// Prepare go-sarah's core context
// Prepare Sarah's core context.
ctx, cancel := context.WithCancel(context.Background())

// Prepare watcher that reads configuration from filesystem
// Prepare a watcher that reads configuration from filesystem.
if config.PluginConfigDir != "" {
configWatcher, _ := watchers.NewFileWatcher(ctx, config.PluginConfigDir)
sarah.RegisterConfigWatcher(configWatcher)
}

// Run
// Run.
err := sarah.Run(ctx, config.Runner)
if err != nil {
panic(err)
Expand Down Expand Up @@ -123,6 +121,6 @@ func setupSlack(config *slack.Config, storage sarah.UserContextStorage) {

bot := sarah.NewBot(adapter, sarah.BotWithStorage(storage))

// Register bot to run.
// Register the bot to run.
sarah.RegisterBot(bot)
}
12 changes: 5 additions & 7 deletions _examples/simple/plugins/count/props.go
@@ -1,10 +1,8 @@
/*
Package count provides example code to setup sarah.CommandProps.
One counter instance is shared between two CommandPropsBuilder.Func,
which means resulting Slack/Gitter Commands access to same counter instance.
This illustrates that, when multiple Bots are registered to Runner, same memory space can be shared.
*/
// Package count provides an example to set up sarah.CommandProps.
//
// One counter instance is shared between two CommandPropsBuilder.Func,
// which means both Slack command and Gitter command access to the same counter instance.
// This illustrates that, when multiple Bots are registered to Runner, same memory space can be shared.
package count

import (
Expand Down
28 changes: 13 additions & 15 deletions _examples/simple/plugins/echo/command.go
@@ -1,11 +1,9 @@
/*
Package echo provides example code to sarah.Command implementation.
CommandProps is a better way to provide set of command properties to Runner
especially when configuration file must be supervised and configuration struct needs to be updated on file update;
Developer may implement Command interface herself and feed its instance to Bot via Bot.AppendCommand
when command specification is simple.
*/
// Package echo provides an example of sarah.Command implementation.
//
// The use of sarah.CommandProps is a better way to provide a set of command properties to Sarah
// especially when a configuration file must be supervised and the configuration values need to be updated on file update.
// When such a configuration supervision is not required, a developer may implement sarah.Command interface herself
// and feed its instance to Sarah via sarah.RegisterCommand or to sarah.Bot via its AppendCommand method.
package echo

import (
Expand All @@ -25,23 +23,23 @@ func (c *command) Identifier() string {
return "echo"
}

// Execute receives user input and returns results of this Command.
// Execute receives a user input and returns a result of this Command execution.
func (c *command) Execute(_ context.Context, input sarah.Input) (*sarah.CommandResponse, error) {
return slack.NewResponse(input, sarah.StripMessage(matchPattern, input.Message()))
}

// Instruction provides input instruction for user.
// Instruction provides a guide for the requesting user.
func (c *command) Instruction(_ *sarah.HelpInput) string {
return ".echo foo"
}

// Match checks if user input matches to this Command.
// Match checks if the user input matches and this Command must be executed.
func (c *command) Match(input sarah.Input) bool {
// Once Runner receives input from Bot, it dispatches task to worker where multiple tasks may run in concurrent manner.
// Searching for corresponding Command is an important part of this task, which means Command.Match is called simultaneously from multiple goroutines.
// To avoid lock contention, Command developer should consider copying the *regexp.Regexp object.
// Once Sarah receives input from sarah.Bot, it dispatches a task to the worker where multiple tasks can run in a concurrent manner.
// Searching for a corresponding Command is an important part of this task, which means Command.Match is called simultaneously from multiple goroutines.
// To avoid a lock contention, Command developer should consider copying the *regexp.Regexp object.
return matchPattern.Copy().MatchString(input.Message())
}

// Command is a command instance that can directly fed to Bot.AppendCommand.
// Command is a command instance that can directly fed to sarah.RegisterCommand or Bot.AppendCommand.
var Command = &command{}
12 changes: 5 additions & 7 deletions _examples/simple/plugins/fixedtimer/props.go
@@ -1,10 +1,8 @@
/*
Package fixedtimer provides example code to setup ScheduledTaskProps with fixed schedule.
The configuration struct, timerConfig, does not implement ScheduledConfig interface,
but instead fixed schedule is provided via ScheduledTaskPropsBuilder.Schedule.
Schedule never changes no matter how many times the configuration file, fixed_timer.yaml, is updated.
*/
// Package fixedtimer provides an example to set up sarah.ScheduledTaskProps with fixed schedule.
//
// The configuration struct, timerConfig, does not implement sarah.ScheduledConfig interface,
// but instead fixed schedule is provided via sarah.ScheduledTaskPropsBuilder's Schedule method.
// The schedule never changes no matter how many times the configuration file, fixed_timer.yaml, is updated.
package fixedtimer

import (
Expand Down
30 changes: 14 additions & 16 deletions _examples/simple/plugins/guess/props.go
@@ -1,13 +1,11 @@
/*
Package guess provides example code to setup stateful command.
This command returns sarah.UserContext as part of sarah.CommandResponse until user inputs correct number.
As long as sarah.UserContext is returned, the next input from the same user is fed to the function defined in sarah.UserContext.
When user guesses right number or input .abort, the context is removed and user is free to input next desired command.
This example uses in-memory storage to store user context.
See https://github.com/oklahomer/go-sarah-rediscontext to use external storage.
*/
// Package guess provides an example to set up a stateful command.
//
// This command returns sarah.UserContext as part of sarah.CommandResponse until the user inputs a correct number.
// As long as sarah.UserContext is returned, the next input from the same user is fed to the function defined in sarah.UserContext.
// When the user guesses the right number or inputs .abort, the user context is removed and the user is free to input the next desired command.
//
// This example uses an in-memory storage to store user contexts.
// See https://github.com/oklahomer/go-sarah-rediscontext to use external storage.
package guess

import (
Expand All @@ -33,11 +31,11 @@ var SlackProps = sarah.NewCommandPropsBuilder().
return strings.HasPrefix(strings.TrimSpace(input.Message()), ".guess")
}).
Func(func(ctx context.Context, input sarah.Input) (*sarah.CommandResponse, error) {
// Generate answer value at the very beginning.
// Generate an answer value at the very beginning.
rand.Seed(time.Now().UnixNano())
answer := rand.Intn(10)

// Let user guess the right answer.
// Let the user guess the right answer.
return slack.NewResponse(
input,
"Input number.",
Expand All @@ -50,19 +48,19 @@ var SlackProps = sarah.NewCommandPropsBuilder().
MustBuild()

func guessFunc(_ context.Context, input sarah.Input, answer int) (*sarah.CommandResponse, error) {
// For handiness, create a function that recursively calls guessFunc until user input right answer.
// For handiness, create a function that recursively calls guessFunc until the user inputs the right answer.
retry := func(c context.Context, i sarah.Input) (*sarah.CommandResponse, error) {
return guessFunc(c, i, answer)
}

// See if user inputs valid number.
// See if the user inputs the valid number.
guess, err := strconv.Atoi(strings.TrimSpace(input.Message()))
if err != nil {
return slack.NewResponse(input, "Invalid input format.", slack.RespWithNext(retry), slack.RespAsThreadReply(true))
}

// If guess is right, tell user and finish current user context.
// Otherwise let user input next guess with bit of a hint.
// If the guess is right, tell the user and finish the current user context.
// Otherwise, let the user input the next guess with a bit of a hint.
if guess == answer {
return slack.NewResponse(input, "Correct!", slack.RespAsThreadReply(true))
} else if guess > answer {
Expand Down
17 changes: 5 additions & 12 deletions _examples/simple/plugins/hello/props.go
@@ -1,15 +1,8 @@
/*
Package hello provides example code to setup relatively simple sarah.CommandProps.
In this example, instead of simply assigning regular expression to CommandPropsBuilder.MatchPattern,
a function is set via CommandPropsBuilder.MatchFunc to do the equivalent task.
With CommandPropsBuilder.MatchFunc, developers may define more complex matching logic than assigning simple regular expression to CommandPropsBuilder.MatchPattern.
One more benefit is that strings package or other packages with higher performance can be used internally like this example.
This sarah.CommandProps can be fed to sarah.NewRunner() as below.
runner, err := sarah.NewRunner(config.Runner, sarah.WithCommandProps(hello.SlackProps), ... )
*/
// Package hello provides an example to set up a relatively simple sarah.CommandProps.
//
// In this example, instead of simply assigning a regular expression to CommandPropsBuilder.MatchPattern,
// a function with a more complex matching logic is set via CommandPropsBuilder.MatchFunc to do the equivalent task.
// One more benefit of using CommandPropsBuilder.MatchFunc is that strings package or other packages with higher performance can be used internally like this example.
package hello

import (
Expand Down
12 changes: 5 additions & 7 deletions _examples/simple/plugins/morning/props.go
@@ -1,9 +1,7 @@
/*
Package morning provides example code to setup sarah.CommandProps with relatively complex matching function.
This setting does not simply provide regular expression via CommandPropsBuilder.MatchPattern,
but instead provide whole matching function to implement complex matcher.
*/
// Package morning provides an example to set up sarah.CommandProps with a relatively complex matching function.
//
// This setting does not simply provide a regular expression via CommandPropsBuilder.MatchPattern,
// but instead provide the whole matching function to implement a complex matcher.
package morning

import (
Expand Down Expand Up @@ -39,7 +37,7 @@ var SlackProps = sarah.NewCommandPropsBuilder().
return false
}

// 2. See if current time between 00:00 - 11:59
// 2. See if the current time is between 00:00 - 11:59
hour := time.Now().Hour()
return hour >= 0 && hour < 12
}).
Expand Down
10 changes: 4 additions & 6 deletions _examples/simple/plugins/timer/props.go
@@ -1,9 +1,7 @@
/*
Package timer provides example code to setup ScheduledTaskProps with re-configurable schedule and sending room.
The configuration struct, timerConfig, implements both ScheduledConfig and DestinatedConfig interface.
The configuration values are read from timer.yaml and Command is re-built when configuration file is updated.
*/
// Package timer provides an example to set up ScheduledTaskProps with re-configurable schedule and sending destination.
//
// The configuration struct, timerConfig, implements both sarah.ScheduledConfig and sarah.DestinatedConfig interface.
// The configuration values are read from timer.yaml and the command is rebuilt when the configuration file is updated.
package timer

import (
Expand Down
22 changes: 10 additions & 12 deletions _examples/simple/plugins/todo/command.go
@@ -1,9 +1,7 @@
/*
Package todo is an example of stateful command that let users input required arguments step by step in a conversational manner.
On each valid input, given argument is stashed to *args.
*args is passed around until all required arguments are filled.
*/
// Package todo provides an example of stateful command that lets users input required arguments step by step in a conversational manner.
//
// On each valid input, the given argument is stashed to *args.
// *args is passed around until all required arguments are filled.
package todo

import (
Expand All @@ -23,7 +21,7 @@ var matchPattern = regexp.MustCompile(`^\.todo`)
type DummyStorage struct {
}

// Save saves given todo settings to permanent storage.
// Save saves the given todo settings to the permanent storage.
func (s *DummyStorage) Save(senderKey string, description string, due time.Time) {
// Write to storage
}
Expand All @@ -33,7 +31,7 @@ type args struct {
due time.Time
}

// BuildCommand builds todo command with given storage.
// BuildCommand builds a todo command with the given storage.
func BuildCommand(storage *DummyStorage) sarah.Command {
return &command{
storage: storage,
Expand All @@ -53,7 +51,7 @@ func (cmd *command) Identifier() string {
func (cmd *command) Execute(_ context.Context, input sarah.Input) (*sarah.CommandResponse, error) {
stripped := sarah.StripMessage(matchPattern, input.Message())
if stripped == "" {
// If description is not given, let user proceed to input one.
// If a description is not given, let the user proceed to input one.
return slack.NewResponse(input, "Please input a thing to do.", slack.RespWithNext(cmd.inputDesc))
}

Expand All @@ -78,14 +76,14 @@ func (cmd *command) Match(input sarah.Input) bool {
func (cmd *command) inputDesc(_ context.Context, input sarah.Input) (*sarah.CommandResponse, error) {
description := strings.TrimSpace(input.Message())
if description == "" {
// If no description is provided, let user input.
// If no description is provided, let the user input.
next := func(c context.Context, i sarah.Input) (*sarah.CommandResponse, error) {
return cmd.inputDesc(c, i)
}
return slack.NewResponse(input, "Please input a thing to do.", slack.RespWithNext(next))
}

// Let user proceed to next step to input due date.
// Let the user proceed to the next step to input a due date.
next := func(c context.Context, i sarah.Input) (*sarah.CommandResponse, error) {
args := &args{
description: description,
Expand All @@ -102,7 +100,7 @@ func (cmd *command) inputDate(_ context.Context, input sarah.Input, args *args)
return cmd.inputDate(c, i, args)
}
if date == "" {
// If no due date is provided, let user input.
// If no due date is provided, let the user input.
return slack.NewResponse(input, "Please input the due date in YYYY-MM-DD format.", slack.RespWithNext(reinput))
}

Expand Down
14 changes: 6 additions & 8 deletions _examples/simple/plugins/worldweather/client.go
Expand Up @@ -13,26 +13,24 @@ const (
weatherAPIEndpointFormat = "https://api.worldweatheronline.com/free/v2/%s.ashx"
)

// Config contains some configuration variables for World Weather.
// Config contains some configuration variables.
type Config struct {
apiKey string
}

// NewConfig returns initialized Config struct with default settings.
// APIKey is empty at this point. This can be set by feeding this instance to json.Unmarshal/yaml.Unmarshal,
// or by direct assignment.
// NewConfig returns a newly initialized Config with the given API key.
func NewConfig(apiKey string) *Config {
return &Config{
apiKey: apiKey,
}
}

// Client is a API client for World Weather.
// Client is an API client for World Weather.
type Client struct {
config *Config
}

// NewClient creates and returns new API client with given Config struct.
// NewClient creates and returns a new API client with the given Config struct.
func NewClient(config *Config) *Client {
return &Client{config: config}
}
Expand All @@ -53,7 +51,7 @@ func (client *Client) buildEndpoint(apiType string, queryParams *url.Values) *ur
return requestURL
}

// Get makes HTTP GET request to World Weather API endpoint.
// Get makes an HTTP GET request to the World Weather API endpoint.
func (client *Client) Get(ctx context.Context, apiType string, queryParams *url.Values, data interface{}) error {
endpoint := client.buildEndpoint(apiType, queryParams)
req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
Expand Down Expand Up @@ -83,7 +81,7 @@ func (client *Client) Get(ctx context.Context, apiType string, queryParams *url.
return nil
}

// LocalWeather fetches given location's weather.
// LocalWeather fetches the given location's weather.
func (client *Client) LocalWeather(ctx context.Context, location string) (*LocalWeatherResponse, error) {
queryParams := &url.Values{}
queryParams.Add("q", location)
Expand Down

0 comments on commit 52e4872

Please sign in to comment.