From 7df064e0747a25dae99d98d3dd380dade0fb9a12 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Fri, 9 Dec 2016 01:14:53 -0800 Subject: [PATCH 1/4] Implement pluggable session store and make it threadsafe --- .env | 1 + Makefile | 4 + cmd/rivescript/main.go | 22 ++-- config/config.go | 54 ++++++++++ rivescript.go | 16 +-- sessions/interface.go | 106 +++++++++++++++++++ sessions/memory/memory.go | 217 ++++++++++++++++++++++++++++++++++++++ sessions/null/null.go | 65 ++++++++++++ src/base_test.go | 30 ++++-- src/brain.go | 30 +++--- src/config.go | 144 +++++++++---------------- src/inheritance.go | 4 +- src/rivescript.go | 42 +++++--- src/sessions_test.go | 84 +++++++++++++++ src/structs.go | 27 ----- src/tags.go | 61 ++++++----- 16 files changed, 707 insertions(+), 200 deletions(-) create mode 100644 .env create mode 100644 config/config.go create mode 100644 sessions/interface.go create mode 100644 sessions/memory/memory.go create mode 100644 sessions/null/null.go create mode 100644 src/sessions_test.go diff --git a/.env b/.env new file mode 100644 index 0000000..b48feb5 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +export GOPATH="$(pwd)/.gopath" diff --git a/Makefile b/Makefile index 3d64cd7..1f57248 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,10 @@ setup: run: gopath GOPATH=$(GOPATH) GO15VENDOREXPERIMENT=1 go run cmd/rivescript/main.go eg/brain +# `make debug` to run the rivescript cmd in debug mode +debug: gopath + GOPATH=$(GOPATH) GO15VENDOREXPERIMENT=1 go run cmd/rivescript/main.go -debug eg/brain + # `make fmt` to run gofmt fmt: gofmt -w . diff --git a/cmd/rivescript/main.go b/cmd/rivescript/main.go index 18925f7..1bc9387 100644 --- a/cmd/rivescript/main.go +++ b/cmd/rivescript/main.go @@ -20,10 +20,12 @@ import ( "bufio" "flag" "fmt" - rivescript "github.com/aichaos/rivescript-go" - js "github.com/aichaos/rivescript-go/lang/javascript" "os" "strings" + + "github.com/aichaos/rivescript-go" + "github.com/aichaos/rivescript-go/config" + "github.com/aichaos/rivescript-go/lang/javascript" ) func main() { @@ -31,7 +33,8 @@ func main() { version := flag.Bool("version", false, "Show the version number and exit.") debug := flag.Bool("debug", false, "Enable debug mode.") utf8 := flag.Bool("utf8", false, "Enable UTF-8 mode.") - depth := flag.Int("depth", 50, "Recursion depth limit (default 50)") + depth := flag.Uint("depth", 50, "Recursion depth limit (default 50)") + nostrict := flag.Bool("nostrict", false, "Disable strict syntax checking") flag.Parse() args := flag.Args() @@ -48,14 +51,15 @@ func main() { root := args[0] // Initialize the bot. - bot := rivescript.New() - bot.SetDebug(*debug) - bot.SetUTF8(*utf8) - bot.SetDepth(*depth) + bot := rivescript.New(&config.Config{ + Debug: *debug, + Strict: !*nostrict, + Depth: *depth, + UTF8: *utf8, + }) // JavaScript object macro handler. - jsHandler := js.New(bot) - bot.SetHandler("javascript", jsHandler) + bot.SetHandler("javascript", javascript.New(bot)) // Load the target directory. err := bot.LoadDirectory(root) diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..203bf8d --- /dev/null +++ b/config/config.go @@ -0,0 +1,54 @@ +// Package config provides the RiveScript configuration type. +package config + +import ( + "github.com/aichaos/rivescript-go/sessions" + "github.com/aichaos/rivescript-go/sessions/memory" +) + +// Type Config configures a RiveScript instance. +type Config struct { + // Debug enables verbose debug logging to your standard output. + Debug bool + + // Strict enables strict syntax checking. + Strict bool + + // Depth sets the recursion depth limit. The zero value will default to + // 50 levels deep. + Depth uint + + // UTF8 enables UTF-8 support for user messages and triggers. + UTF8 bool + + // SessionManager chooses a session manager for user variables. + SessionManager sessions.SessionManager +} + +// Basic creates a default configuration: +// +// - Strict: true +// - Depth: 50 +// - UTF8: false +func Basic() *Config { + return &Config{ + Strict: true, + Depth: 50, + UTF8: false, + SessionManager: memory.New(), + } +} + +// UTF8 creates a default configuration with UTF-8 mode enabled. +// +// - Strict: true +// - Depth: 50 +// - UTF8: true +func UTF8() *Config { + return &Config{ + Strict: true, + Depth: 50, + UTF8: true, + SessionManager: memory.New(), + } +} diff --git a/rivescript.go b/rivescript.go index 546b983..2899862 100644 --- a/rivescript.go +++ b/rivescript.go @@ -13,7 +13,9 @@ package rivescript */ import ( + "github.com/aichaos/rivescript-go/config" "github.com/aichaos/rivescript-go/macro" + "github.com/aichaos/rivescript-go/sessions" "github.com/aichaos/rivescript-go/src" ) @@ -23,9 +25,9 @@ type RiveScript struct { rs *src.RiveScript } -func New() *RiveScript { +func New(config *config.Config) *RiveScript { bot := new(RiveScript) - bot.rs = src.New() + bot.rs = src.New(config) return bot } @@ -62,12 +64,12 @@ func (self *RiveScript) SetUnicodePunctuation(value string) { } // SetDepth lets you override the recursion depth limit (default 50). -func (self *RiveScript) SetDepth(value int) { +func (self *RiveScript) SetDepth(value uint) { self.rs.Depth = value } // GetDepth returns the current recursion depth limit. -func (self *RiveScript) GetDepth() int { +func (self *RiveScript) GetDepth() uint { return self.rs.Depth } @@ -277,7 +279,7 @@ GetUservars gets all the variables for a user. This returns a `map[string]string` containing all the user's variables. */ -func (self *RiveScript) GetUservars(username string) (map[string]string, error) { +func (self *RiveScript) GetUservars(username string) (*sessions.UserData, error) { return self.rs.GetUservars(username) } @@ -287,7 +289,7 @@ GetAllUservars gets all the variables for all the users. This returns a map of username (strings) to `map[string]string` of their variables. */ -func (self *RiveScript) GetAllUservars() map[string]map[string]string { +func (self *RiveScript) GetAllUservars() map[string]*sessions.UserData { return self.rs.GetAllUservars() } @@ -319,7 +321,7 @@ The `action` can be one of the following: * discard: Don't restore the variables, just delete the frozen copy. * keep: Keep the frozen copy after restoring. */ -func (self *RiveScript) ThawUservars(username, action string) error { +func (self *RiveScript) ThawUservars(username string, action sessions.ThawAction) error { return self.rs.ThawUservars(username, action) } diff --git a/sessions/interface.go b/sessions/interface.go new file mode 100644 index 0000000..b3a9275 --- /dev/null +++ b/sessions/interface.go @@ -0,0 +1,106 @@ +// Package sessions provides the interface and default session store for +// RiveScript. +package sessions + +/* +Interface SessionManager describes a session manager for user variables +in RiveScript. + +The session manager keeps track of getting and setting user variables, +for example when the `` or `` tags are used in RiveScript +or when API functions like `SetUservar()` are called. + +By default RiveScript stores user sessions in memory and provides methods +to export and import them (e.g. to persist them when the bot shuts down +so they can be reloaded). If you'd prefer a more 'active' session storage, +for example one that puts user variables into a database or cache, you can +create your own session manager that implements this interface. +*/ +type SessionManager interface { + // Init makes sure a username has a session (creates one if not). It returns + // the pointer to the user data in either case. + Init(username string) *UserData + + // Set user variables from a map. + Set(username string, vars map[string]string) + + // AddHistory adds input and reply to the user's history. + AddHistory(username, input, reply string) + + // SetLastMatch sets the last matched trigger. + SetLastMatch(username, trigger string) + + // Get a user variable. + Get(username string, key string) (string, error) + + // Get all variables for a user. + GetAny(username string) (*UserData, error) + + // Get all variables about all users. + GetAll() map[string]*UserData + + // GetLastMatch returns the last trigger the user matched. + GetLastMatch(username string) (string, error) + + // GetHistory returns the user's history. + GetHistory(username string) (*History, error) + + // Clear all variables for a given user. + Clear(username string) + + // Clear all variables for all users. + ClearAll() + + // Freeze makes a snapshot of a user's variables. + Freeze(string) error + + // Thaw unfreezes a snapshot of a user's variables and returns an error + // if the user had no frozen variables. + Thaw(username string, ThawAction ThawAction) error +} + +// HistorySize is the number of entries stored in the history. +const HistorySize int = 9 + +// Type UserData is a container for user variables. +type UserData struct { + Variables map[string]string + LastMatch string + *History +} + +// Type History keeps track of recent input and reply history. +type History struct { + Input []string + Reply []string +} + +// NewHistory creates a new History object with the history arrays filled out. +func NewHistory() *History { + h := &History{ + Input: []string{}, + Reply: []string{}, + } + + for i := 0; i < HistorySize; i++ { + h.Input = append(h.Input, "undefined") + h.Reply = append(h.Reply, "undefined") + } + + return h +} + +// Type ThawAction describes the action for the `Thaw()` method. +type ThawAction int + +// Valid options for ThawAction. +const ( + // Thaw means to restore the user variables and erase the frozen copy. + Thaw = iota + + // Discard means to cancel the frozen copy and not restore them. + Discard + + // Keep means to restore the user variables and still keep the frozen copy. + Keep +) diff --git a/sessions/memory/memory.go b/sessions/memory/memory.go new file mode 100644 index 0000000..ad4d800 --- /dev/null +++ b/sessions/memory/memory.go @@ -0,0 +1,217 @@ +// Package memory provides the default in-memory session store. +package memory + +import ( + "fmt" + "strings" + "sync" + + "github.com/aichaos/rivescript-go/sessions" +) + +// Type MemoryStore implements the default in-memory session store for +// RiveScript. +type MemoryStore struct { + lock sync.Mutex + users map[string]*sessions.UserData + frozen map[string]*sessions.UserData +} + +// New creates a new MemoryStore. +func New() *MemoryStore { + return &MemoryStore{ + users: map[string]*sessions.UserData{}, + frozen: map[string]*sessions.UserData{}, + } +} + +// init makes sure a username exists in the memory store. +func (s *MemoryStore) Init(username string) *sessions.UserData { + s.lock.Lock() + defer s.lock.Unlock() + + if _, ok := s.users[username]; !ok { + s.users[username] = defaultSession() + } + return s.users[username] +} + +// Set a user variable. +func (s *MemoryStore) Set(username string, vars map[string]string) { + s.Init(username) + s.lock.Lock() + defer s.lock.Unlock() + + for k, v := range vars { + s.users[username].Variables[k] = v + } +} + +// AddHistory adds history items. +func (s *MemoryStore) AddHistory(username, input, reply string) { + data := s.Init(username) + s.lock.Lock() + defer s.lock.Unlock() + + data.History.Input = data.History.Input[:len(data.History.Input)-1] // Pop + data.History.Input = append([]string{strings.TrimSpace(input)}, data.History.Input...) // Unshift + data.History.Reply = data.History.Reply[:len(data.History.Reply)-1] // Pop + data.History.Reply = append([]string{strings.TrimSpace(reply)}, data.History.Reply...) // Unshift +} + +// SetLastMatch sets the user's last matched trigger. +func (s *MemoryStore) SetLastMatch(username, trigger string) { + data := s.Init(username) + s.lock.Lock() + defer s.lock.Unlock() + data.LastMatch = trigger +} + +// Get a user variable. +func (s *MemoryStore) Get(username string, name string) (string, error) { + s.lock.Lock() + defer s.lock.Unlock() + + if _, ok := s.users[username]; !ok { + return "", fmt.Errorf(`no data for username "%s"`, username) + } + + value, ok := s.users[username].Variables[name] + if !ok { + return "undefined", fmt.Errorf(`variable "%s" for user "%s" not set`, name, username) + } + + return value, nil +} + +// GetAny gets all variables for a user. +func (s *MemoryStore) GetAny(username string) (*sessions.UserData, error) { + s.lock.Lock() + defer s.lock.Unlock() + + if _, ok := s.users[username]; !ok { + return &sessions.UserData{}, fmt.Errorf(`no data for username "%s"`, username) + } + return cloneUser(s.users[username]), nil +} + +// GetAll gets all data for all users. +func (s *MemoryStore) GetAll() map[string]*sessions.UserData { + s.lock.Lock() + defer s.lock.Unlock() + + // Make safe copies of all our structures. + var result map[string]*sessions.UserData + for k, v := range s.users { + result[k] = cloneUser(v) + } + return result +} + +// GetLastMatch returns the last matched trigger for the user, +func (s *MemoryStore) GetLastMatch(username string) (string, error) { + s.lock.Lock() + defer s.lock.Unlock() + + data, ok := s.users[username] + if !ok { + return "", fmt.Errorf(`no data for username "%s"`, username) + } + return data.LastMatch, nil +} + +// GetHistory gets the user's history. +func (s *MemoryStore) GetHistory(username string) (*sessions.History, error) { + s.lock.Lock() + defer s.lock.Unlock() + + data, ok := s.users[username] + if !ok { + return nil, fmt.Errorf(`no data for username "%s"`, username) + } + return data.History, nil +} + +// Clear data for a user. +func (s *MemoryStore) Clear(username string) { + s.lock.Lock() + defer s.lock.Unlock() + + delete(s.users, username) +} + +// ClearAll resets all user data for all users. +func (s *MemoryStore) ClearAll() { + s.lock.Lock() + defer s.lock.Unlock() + + s.users = make(map[string]*sessions.UserData) + s.frozen = make(map[string]*sessions.UserData) +} + +// Freeze makes a snapshot of user variables. +func (s *MemoryStore) Freeze(username string) error { + s.lock.Lock() + defer s.lock.Unlock() + + data, ok := s.users[username] + if !ok { + return fmt.Errorf(`no data for username %s`, username) + } + + s.frozen[username] = cloneUser(data) + return nil +} + +// Thaw restores from a snapshot. +func (s *MemoryStore) Thaw(username string, action sessions.ThawAction) error { + s.lock.Lock() + defer s.lock.Unlock() + + frozen, ok := s.frozen[username] + if !ok { + return fmt.Errorf(`no frozen data for username "%s"`, username) + } + + if action == sessions.Thaw { + s.users[username] = cloneUser(frozen) + delete(s.frozen, username) + } else if action == sessions.Discard { + delete(s.frozen, username) + } else if action == sessions.Keep { + s.users[username] = cloneUser(frozen) + } + + return nil +} + +// cloneUser makes a safe clone of a UserData. +func cloneUser(data *sessions.UserData) *sessions.UserData { + new := defaultSession() + + // Copy user variables. + for k, v := range data.Variables { + new.Variables[k] = v + } + + // Copy history. + for i := 0; i < sessions.HistorySize; i++ { + new.History.Input[i] = data.History.Input[i] + new.History.Reply[i] = data.History.Reply[i] + } + + return new +} + +// defaultSession initializes the default session variables for a user. +// This mostly just means the topic is set to "random" and structs +// are initialized. +func defaultSession() *sessions.UserData { + return &sessions.UserData{ + Variables: map[string]string{ + "topic": "random", + }, + LastMatch: "", + History: sessions.NewHistory(), + } +} diff --git a/sessions/null/null.go b/sessions/null/null.go new file mode 100644 index 0000000..5f90ed8 --- /dev/null +++ b/sessions/null/null.go @@ -0,0 +1,65 @@ +// Package null provides a session manager that has no memory. +package null + +import "github.com/aichaos/rivescript-go/sessions" + +// Type NullStore implements a memory store that has no memory. +// +// It's mostly useful for the unit tests. With this memory store in place, +// RiveScript is unable to maintain any user variables at all. +type NullStore struct{} + +// New creates a new NullStore. +func New() *NullStore { + return new(NullStore) +} + +func (s *NullStore) Init(username string) *sessions.UserData { + return nullSession() +} + +func (s *NullStore) Set(username string, vars map[string]string) {} + +func (s *NullStore) AddHistory(username, input, reply string) {} + +func (s *NullStore) SetLastMatch(username, trigger string) {} + +func (s *NullStore) Get(username string, name string) (string, error) { + return "undefined", nil +} + +func (s *NullStore) GetAny(username string) (*sessions.UserData, error) { + return nullSession(), nil +} + +func (s *NullStore) GetAll() map[string]*sessions.UserData { + return map[string]*sessions.UserData{} +} + +func (s *NullStore) GetLastMatch(username string) (string, error) { + return "", nil +} + +func (s *NullStore) GetHistory(username string) (*sessions.History, error) { + return sessions.NewHistory(), nil +} + +func (s *NullStore) Clear(username string) {} + +func (s *NullStore) ClearAll() {} + +func (s *NullStore) Freeze(username string) error { + return nil +} + +func (s *NullStore) Thaw(username string, action sessions.ThawAction) error { + return nil +} + +func nullSession() *sessions.UserData { + return &sessions.UserData{ + Variables: map[string]string{}, + History: sessions.NewHistory(), + LastMatch: "", + } +} diff --git a/src/base_test.go b/src/base_test.go index 964ab91..3c25812 100644 --- a/src/base_test.go +++ b/src/base_test.go @@ -5,8 +5,10 @@ package src_test import ( "fmt" - rivescript "github.com/aichaos/rivescript-go" "testing" + + "github.com/aichaos/rivescript-go" + "github.com/aichaos/rivescript-go/config" ) type RiveScriptTest struct { @@ -16,18 +18,33 @@ type RiveScriptTest struct { } func NewTest(t *testing.T) *RiveScriptTest { - tester := new(RiveScriptTest) - tester.bot = rivescript.New() - tester.t = t - tester.username = "soandso" - return tester + return &RiveScriptTest{ + bot: rivescript.New(config.Basic()), + t: t, + username: "soandso", + } +} + +func NewTestWithConfig(t *testing.T, config *config.Config) *RiveScriptTest { + return &RiveScriptTest{ + bot: rivescript.New(config), + t: t, + username: "soandso", + } +} + +// RS exposes the underlying RiveScript API. +func (rst *RiveScriptTest) RS() *rivescript.RiveScript { + return rst.bot } +// extend updates the RiveScript source code. func (rst RiveScriptTest) extend(code string) { rst.bot.Stream(code) rst.bot.SortReplies() } +// reply asserts that a given input gets the expected reply. func (rst RiveScriptTest) reply(message string, expected string) { reply := rst.bot.Reply(rst.username, message) if reply != expected { @@ -35,6 +52,7 @@ func (rst RiveScriptTest) reply(message string, expected string) { } } +// uservar asserts a user variable. func (rst RiveScriptTest) uservar(name string, expected string) { value, _ := rst.bot.GetUservar(rst.username, name) if value != expected { diff --git a/src/brain.go b/src/brain.go index 0ac460d..8775a85 100644 --- a/src/brain.go +++ b/src/brain.go @@ -14,9 +14,7 @@ func (rs *RiveScript) Reply(username string, message string) string { rs.say("Asked to reply to [%s] %s", username, message) // Initialize a user profile for this user? - if _, ok := rs.users[username]; !ok { - rs.users[username] = newUser() - } + rs.sessions.Init(username) // Store the current user's ID. rs.currentUser = username @@ -42,11 +40,7 @@ func (rs *RiveScript) Reply(username string, message string) string { } // Save their message history. - user := rs.users[username] - user.inputHistory = user.inputHistory[:len(user.inputHistory)-1] // Pop - user.inputHistory = append([]string{strings.TrimSpace(message)}, user.inputHistory...) // Unshift - user.replyHistory = user.replyHistory[:len(user.replyHistory)-1] // Pop - user.replyHistory = append([]string{strings.TrimSpace(reply)}, user.replyHistory...) // Unshift + rs.sessions.AddHistory(username, message, reply) // Unset the current user's ID. rs.currentUser = "" @@ -64,7 +58,7 @@ Parameters isBegin: Whether this reply is for the "BEGIN Block" context or not. step: Recursion depth counter. */ -func (rs *RiveScript) getReply(username string, message string, isBegin bool, step int) string { +func (rs *RiveScript) getReply(username string, message string, isBegin bool, step uint) string { // Needed to sort replies? if len(rs.sorted.topics) == 0 { rs.warn("You forgot to call SortReplies()!") @@ -72,7 +66,10 @@ func (rs *RiveScript) getReply(username string, message string, isBegin bool, st } // Collect data on this user. - topic := rs.users[username].data["topic"] + topic, err := rs.sessions.Get(username, "topic") + if err != nil { + topic = "random" + } stars := []string{} thatStars := []string{} // For %Previous reply := "" @@ -80,7 +77,7 @@ func (rs *RiveScript) getReply(username string, message string, isBegin bool, st // Avoid letting them fall into a missing topic. if _, ok := rs.topics[topic]; !ok { rs.warn("User %s was in an empty topic named '%s'", username, topic) - rs.users[username].data["topic"] = "random" + rs.sessions.Set(username, map[string]string{"topic": "random"}) topic = "random" } @@ -125,7 +122,8 @@ func (rs *RiveScript) getReply(username string, message string, isBegin bool, st rs.say("There's a %%Previous in this topic!") // Get the bot's last reply to the user. - lastReply := rs.users[username].replyHistory[0] + history, _ := rs.sessions.GetHistory(username) + lastReply := history.Reply[0] // Format the bot's reply the same way as the human's. lastReply = rs.formatMessage(lastReply, true) @@ -235,7 +233,7 @@ func (rs *RiveScript) getReply(username string, message string, isBegin bool, st } // Store what trigger they matched on. - rs.users[username].lastMatch = matchedTrigger + rs.sessions.SetLastMatch(username, matchedTrigger) // Did we match? if foundMatch { @@ -360,7 +358,7 @@ func (rs *RiveScript) getReply(username string, message string, isBegin bool, st // Topic setter match := re_topic.FindStringSubmatch(reply) - giveup := 0 + var giveup uint for len(match) > 0 { giveup++ if giveup > rs.Depth { @@ -368,7 +366,7 @@ func (rs *RiveScript) getReply(username string, message string, isBegin bool, st break } name := match[1] - rs.users[username].data["topic"] = name + rs.sessions.Set(username, map[string]string{"topic": name}) reply = strings.Replace(reply, fmt.Sprintf("{topic=%s}", name), "", -1) match = re_topic.FindStringSubmatch(reply) } @@ -384,7 +382,7 @@ func (rs *RiveScript) getReply(username string, message string, isBegin bool, st } name := match[1] value := match[2] - rs.users[username].data[name] = value + rs.sessions.Set(username, map[string]string{name: value}) reply = strings.Replace(reply, fmt.Sprintf("", name, value), "", -1) match = re_set.FindStringSubmatch(reply) } diff --git a/src/config.go b/src/config.go index edc929c..ac9802e 100644 --- a/src/config.go +++ b/src/config.go @@ -4,26 +4,43 @@ package src import ( "errors" + "github.com/aichaos/rivescript-go/macro" + "github.com/aichaos/rivescript-go/sessions" ) func (rs *RiveScript) SetHandler(lang string, handler macro.MacroInterface) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + rs.handlers[lang] = handler } func (rs *RiveScript) RemoveHandler(lang string) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + delete(rs.handlers, lang) } func (rs *RiveScript) SetSubroutine(name string, fn Subroutine) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + rs.subroutines[name] = fn } func (rs *RiveScript) DeleteSubroutine(name string) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + delete(rs.subroutines, name) } func (rs *RiveScript) SetGlobal(name string, value string) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + if value == "undefined" { delete(rs.global, name) } else { @@ -32,6 +49,9 @@ func (rs *RiveScript) SetGlobal(name string, value string) { } func (rs *RiveScript) SetVariable(name string, value string) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + if value == "undefined" { delete(rs.var_, name) } else { @@ -40,6 +60,9 @@ func (rs *RiveScript) SetVariable(name string, value string) { } func (rs *RiveScript) SetSubstitution(name string, value string) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + if value == "undefined" { delete(rs.sub, name) } else { @@ -48,6 +71,9 @@ func (rs *RiveScript) SetSubstitution(name string, value string) { } func (rs *RiveScript) SetPerson(name string, value string) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + if value == "undefined" { delete(rs.person, name) } else { @@ -56,34 +82,19 @@ func (rs *RiveScript) SetPerson(name string, value string) { } func (rs *RiveScript) SetUservar(username string, name string, value string) { - // Initialize the user? - if _, ok := rs.users[username]; !ok { - rs.users[username] = newUser() - } - - if value == "undefined" { - delete(rs.users[username].data, name) - } else { - rs.users[username].data[name] = value - } + rs.sessions.Set(username, map[string]string{ + name: value, + }) } func (rs *RiveScript) SetUservars(username string, data map[string]string) { - // Initialize the user? - if _, ok := rs.users[username]; !ok { - rs.users[username] = newUser() - } - - for key, value := range data { - if value == "undefined" { - delete(rs.users[username].data, key) - } else { - rs.users[username].data[key] = value - } - } + rs.sessions.Set(username, data) } func (rs *RiveScript) GetGlobal(name string) (string, error) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + if _, ok := rs.global[name]; ok { return rs.global[name], nil } @@ -91,6 +102,9 @@ func (rs *RiveScript) GetGlobal(name string) (string, error) { } func (rs *RiveScript) GetVariable(name string) (string, error) { + rs.cLock.Lock() + defer rs.cLock.Unlock() + if _, ok := rs.var_[name]; ok { return rs.var_[name], nil } @@ -98,95 +112,35 @@ func (rs *RiveScript) GetVariable(name string) (string, error) { } func (rs *RiveScript) GetUservar(username string, name string) (string, error) { - if _, ok := rs.users[username]; ok { - if _, ok := rs.users[username].data[name]; ok { - return rs.users[username].data[name], nil - } - } - return "undefined", errors.New("User variable not found.") + return rs.sessions.Get(username, name) } -func (rs *RiveScript) GetUservars(username string) (map[string]string, error) { - if _, ok := rs.users[username]; ok { - return rs.users[username].data, nil - } - return map[string]string{}, errors.New("Username not found.") +func (rs *RiveScript) GetUservars(username string) (*sessions.UserData, error) { + return rs.sessions.GetAny(username) } -func (rs *RiveScript) GetAllUservars() map[string]map[string]string { - result := map[string]map[string]string{} - for username, data := range rs.users { - result[username] = data.data - } - return result +func (rs *RiveScript) GetAllUservars() map[string]*sessions.UserData { + return rs.sessions.GetAll() } func (rs *RiveScript) ClearUservars(username string) { - delete(rs.users, username) + rs.sessions.Clear(username) } func (rs *RiveScript) ClearAllUservars() { - for username, _ := range rs.users { - delete(rs.users, username) - } + rs.sessions.ClearAll() } func (rs *RiveScript) FreezeUservars(username string) error { - if _, ok := rs.users[username]; ok { - delete(rs.freeze, username) // Always start fresh - rs.freeze[username] = newUser() - - for key, value := range rs.users[username].data { - rs.freeze[username].data[key] = value - } - - for i, entry := range rs.users[username].inputHistory { - rs.freeze[username].inputHistory[i] = entry - } - for i, entry := range rs.users[username].replyHistory { - rs.freeze[username].replyHistory[i] = entry - } - return nil - } - return errors.New("Username not found.") -} - -func (rs *RiveScript) ThawUservars(username string, action string) error { - if _, ok := rs.freeze[username]; ok { - // What are we doing? - if action == "thaw" { - rs.ClearUservars(username) - rs.users[username] = rs.freeze[username] - delete(rs.freeze, username) - } else if action == "discard" { - delete(rs.freeze, username) - } else if action == "keep" { - delete(rs.users, username) // Always start fresh - rs.users[username] = newUser() - - for key, value := range rs.freeze[username].data { - rs.users[username].data[key] = value - } - - for i, entry := range rs.freeze[username].inputHistory { - rs.users[username].inputHistory[i] = entry - } - for i, entry := range rs.freeze[username].replyHistory { - rs.users[username].replyHistory[i] = entry - } - } else { - return errors.New("Unsupported thaw action. Valid options are: thaw, discard, keep.") - } - return nil - } - return errors.New("Username not found.") + return rs.sessions.Freeze(username) +} + +func (rs *RiveScript) ThawUservars(username string, action sessions.ThawAction) error { + return rs.sessions.Thaw(username, action) } func (rs *RiveScript) LastMatch(username string) (string, error) { - if _, ok := rs.users[username]; ok { - return rs.users[username].lastMatch, nil - } - return "", errors.New("Username not found.") + return rs.sessions.GetLastMatch(username) } func (rs *RiveScript) CurrentUser() string { diff --git a/src/inheritance.go b/src/inheritance.go index 808e50c..5f2cb4d 100644 --- a/src/inheritance.go +++ b/src/inheritance.go @@ -51,7 +51,7 @@ The inherited option is true if this is a recursive call, from a topic that inherits other topics. This forces the {inherits} tag to be added to the triggers. This only applies when the topic 'includes' another topic. */ -func (rs *RiveScript) _getTopicTriggers(topic string, topics map[string]*astTopic, thats map[string]*thatTopic, depth int, inheritance int, inherited bool) []sortedTriggerEntry { +func (rs *RiveScript) _getTopicTriggers(topic string, topics map[string]*astTopic, thats map[string]*thatTopic, depth uint, inheritance int, inherited bool) []sortedTriggerEntry { // Break if we're in too deep. if depth > rs.Depth { rs.warn("Deep recursion while scanning topic inheritance!") @@ -141,7 +141,7 @@ getTopicTree returns an array of every topic related to a topic (all the topics it inherits or includes, plus all the topics included or inherited by those topics, and so on). The array includes the original topic, too. */ -func (rs *RiveScript) getTopicTree(topic string, depth int) []string { +func (rs *RiveScript) getTopicTree(topic string, depth uint) []string { // Break if we're in too deep. if depth > rs.Depth { rs.warn("Deep recursion while scanning topic tree!") diff --git a/src/rivescript.go b/src/rivescript.go index 08148ba..e323eb1 100644 --- a/src/rivescript.go +++ b/src/rivescript.go @@ -19,16 +19,21 @@ You've been warned. Here be dragons. package src import ( + "regexp" + "sync" + + "github.com/aichaos/rivescript-go/config" "github.com/aichaos/rivescript-go/macro" "github.com/aichaos/rivescript-go/parser" - "regexp" + "github.com/aichaos/rivescript-go/sessions" + "github.com/aichaos/rivescript-go/sessions/memory" ) type RiveScript struct { // Parameters Debug bool // Debug mode Strict bool // Strictly enforce RiveScript syntax - Depth int // Max depth for recursion + Depth uint // Max depth for recursion UTF8 bool // Support UTF-8 RiveScript code UnicodePunctuation *regexp.Regexp @@ -36,13 +41,13 @@ type RiveScript struct { parser *parser.Parser // Internal data structures + cLock sync.Mutex // Lock for config variables. global map[string]string // 'global' variables var_ map[string]string // 'var' bot variables sub map[string]string // 'sub' substitutions person map[string]string // 'person' substitutions array map[string][]string // 'array' - users map[string]*userData // user variables - freeze map[string]*userData // frozen user variables + sessions sessions.SessionManager // user variable session manager includes map[string]map[string]bool // included topics inherits map[string]map[string]bool // inherited topics objlangs map[string]string // object macro languages @@ -60,12 +65,25 @@ type RiveScript struct { * Constructor and Debug Methods * ******************************************************************************/ -func New() *RiveScript { +func New(config *config.Config) *RiveScript { rs := new(RiveScript) - rs.Debug = false - rs.Strict = true - rs.Depth = 50 - rs.UTF8 = false + if config != nil { + if config.SessionManager == nil { + rs.say("No SessionManager config: using default MemoryStore") + config.SessionManager = memory.New() + } + + if config.Depth <= 0 { + rs.say("No depth config: using default 50") + config.Depth = 50 + } + + rs.Debug = config.Debug + rs.Strict = config.Strict + rs.UTF8 = config.UTF8 + rs.Depth = config.Depth + rs.sessions = config.SessionManager + } rs.UnicodePunctuation = regexp.MustCompile(`[.,!?;:]`) // Initialize helpers. @@ -82,8 +100,6 @@ func New() *RiveScript { rs.sub = map[string]string{} rs.person = map[string]string{} rs.array = map[string][]string{} - rs.users = map[string]*userData{} - rs.freeze = map[string]*userData{} rs.includes = map[string]map[string]bool{} rs.inherits = map[string]map[string]bool{} rs.objlangs = map[string]string{} @@ -93,8 +109,6 @@ func New() *RiveScript { rs.thats = map[string]*thatTopic{} rs.sorted = new(sortBuffer) - // Initialize Golang handler. - //rs.handlers["go"] = new(golangHandler) return rs } @@ -110,7 +124,7 @@ func (rs *RiveScript) SetStrict(value bool) { rs.Strict = value } -func (rs *RiveScript) SetDepth(value int) { +func (rs *RiveScript) SetDepth(value uint) { rs.Depth = value } diff --git a/src/sessions_test.go b/src/sessions_test.go new file mode 100644 index 0000000..8991592 --- /dev/null +++ b/src/sessions_test.go @@ -0,0 +1,84 @@ +package src_test + +import ( + "testing" + + "github.com/aichaos/rivescript-go/config" + "github.com/aichaos/rivescript-go/sessions" + "github.com/aichaos/rivescript-go/sessions/memory" + "github.com/aichaos/rivescript-go/sessions/null" +) + +var commonSessionTest = ` + + my name is * + - >Nice to meet you, . + + + who am i + - Aren't you ? + + + what did i just say + - You just said: + + + what did you just say + - I just said: + + + i hate you + - How mean!{topic=apology} + + > topic apology + + * + - Nope, I'm mad at you. + < topic +` + +func TestNullSession(t *testing.T) { + bot := NewTestWithConfig(t, &config.Config{ + SessionManager: null.New(), + }) + bot.extend(commonSessionTest) + bot.reply("My name is Aiden", "Nice to meet you, undefined.") + bot.reply("Who am I?", "Aren't you undefined?") + bot.reply("What did I just say?", "You just said: undefined") + bot.reply("What did you just say?", "I just said: undefined") + bot.reply("I hate you", "How mean!") + bot.reply("My name is Aiden", "Nice to meet you, undefined.") +} + +func TestMemorySession(t *testing.T) { + bot := NewTestWithConfig(t, &config.Config{ + SessionManager: memory.New(), + }) + bot.extend(commonSessionTest) + bot.reply("My name is Aiden", "Nice to meet you, Aiden.") + bot.reply("What did I just say?", "You just said: my name is aiden") + bot.reply("Who am I?", "Aren't you Aiden?") + bot.reply("What did you just say?", "I just said: Aren't you Aiden?") + bot.reply("I hate you!", "How mean!") + bot.reply("My name is Bob", "Nope, I'm mad at you.") +} + +func TestFreezeThaw(t *testing.T) { + bot := NewTest(t) + bot.extend(` + + my name is * + - >Nice to meet you, . + + + who am i + - Aren't you ? + `) + bot.reply("My name is Aiden", "Nice to meet you, Aiden.") + bot.reply("Who am I?", "Aren't you Aiden?") + + bot.RS().FreezeUservars(bot.username) + bot.reply("My name is Bob", "Nice to meet you, Bob.") + bot.reply("Who am I?", "Aren't you Bob?") + + bot.RS().ThawUservars(bot.username, sessions.Thaw) + bot.reply("Who am I?", "Aren't you Aiden?") + bot.RS().FreezeUservars(bot.username) + + bot.reply("My name is Bob", "Nice to meet you, Bob.") + bot.reply("Who am I?", "Aren't you Bob?") + bot.RS().ThawUservars(bot.username, sessions.Discard) + bot.reply("Who am I?", "Aren't you Bob?") +} diff --git a/src/structs.go b/src/structs.go index 8839ff3..7c52c92 100644 --- a/src/structs.go +++ b/src/structs.go @@ -6,33 +6,6 @@ package src // TODO: get this exportable to third party devs somehow type Subroutine func(*RiveScript, []string) string -// User data, key/value pairs about the user. -type userData struct { - data map[string]string - lastMatch string - inputHistory []string - replyHistory []string -} - -// newUser creates a new user profile. -func newUser() *userData { - user := new(userData) - user.data = map[string]string{} - user.data["topic"] = "random" - - user.lastMatch = "" - - user.inputHistory = []string{ - "undefined", "undefined", "undefined", "undefined", "undefined", - "undefined", "undefined", "undefined", "undefined", - } - user.replyHistory = []string{ - "undefined", "undefined", "undefined", "undefined", "undefined", - "undefined", "undefined", "undefined", "undefined", - } - return user -} - // Sort buffer data, for RiveScript.SortReplies() type sortBuffer struct { topics map[string][]sortedTriggerEntry // Topic name -> array of triggers diff --git a/src/tags.go b/src/tags.go index 4b6f5e2..588fcb8 100644 --- a/src/tags.go +++ b/src/tags.go @@ -8,6 +8,8 @@ import ( "regexp" "strconv" "strings" + + "github.com/aichaos/rivescript-go/sessions" ) // formatMessage formats a user's message for safe processing. @@ -58,7 +60,7 @@ func (rs *RiveScript) triggerRegexp(username string, pattern string) string { // Optionals. match := re_optional.FindStringSubmatch(pattern) - giveup := 0 + var giveup uint for len(match) > 0 { giveup++ if giveup > rs.Depth { @@ -140,8 +142,8 @@ func (rs *RiveScript) triggerRegexp(username string, pattern string) string { if len(match) > 0 { name := match[1] rep := "undefined" - if _, ok := rs.users[username].data[name]; ok { - rep = rs.users[username].data[name] + if value, err := rs.sessions.Get(username, name); err == nil { + rep = value } pattern = strings.Replace(pattern, fmt.Sprintf(``, name), strings.ToLower(rep), -1) } @@ -157,11 +159,17 @@ func (rs *RiveScript) triggerRegexp(username string, pattern string) string { break } - for i := 1; i <= 9; i++ { + for i := 1; i <= sessions.HistorySize; i++ { inputPattern := fmt.Sprintf("", i) replyPattern := fmt.Sprintf("", i) - pattern = strings.Replace(pattern, inputPattern, rs.users[username].inputHistory[i-1], -1) - pattern = strings.Replace(pattern, replyPattern, rs.users[username].replyHistory[i-1], -1) + history, err := rs.sessions.GetHistory(username) + if err == nil { + pattern = strings.Replace(pattern, inputPattern, history.Input[i-1], -1) + pattern = strings.Replace(pattern, replyPattern, history.Reply[i-1], -1) + } else { + pattern = strings.Replace(pattern, inputPattern, "undefined", -1) + pattern = strings.Replace(pattern, replyPattern, "undefined", -1) + } } } @@ -186,7 +194,7 @@ Params: bst: Array of matched bot stars in a %Previous. step: Recursion depth counter. */ -func (rs *RiveScript) processTags(username string, message string, reply string, st []string, bst []string, step int) string { +func (rs *RiveScript) processTags(username string, message string, reply string, st []string, bst []string, step uint) string { // Prepare the stars and botstars. stars := []string{""} stars = append(stars, st...) @@ -223,9 +231,12 @@ func (rs *RiveScript) processTags(username string, message string, reply string, // and reply = strings.Replace(reply, "", "", -1) reply = strings.Replace(reply, "", "", -1) - for i := 1; i <= 9; i++ { - reply = strings.Replace(reply, fmt.Sprintf("", i), rs.users[username].inputHistory[i-1], -1) - reply = strings.Replace(reply, fmt.Sprintf("", i), rs.users[username].replyHistory[i-1], -1) + history, err := rs.sessions.GetHistory(username) + if err == nil { + for i := 1; i <= sessions.HistorySize; i++ { + reply = strings.Replace(reply, fmt.Sprintf("", i), history.Input[i-1], -1) + reply = strings.Replace(reply, fmt.Sprintf("", i), history.Reply[i-1], -1) + } } // and escape codes. @@ -236,7 +247,7 @@ func (rs *RiveScript) processTags(username string, message string, reply string, // {random} match := re_random.FindStringSubmatch(reply) - giveup := 0 + var giveup uint for len(match) > 0 { giveup++ if giveup > rs.Depth { @@ -265,8 +276,8 @@ func (rs *RiveScript) processTags(username string, message string, reply string, formats := []string{"person", "formal", "sentence", "uppercase", "lowercase"} for _, format := range formats { formatRegexp := regexp.MustCompile(fmt.Sprintf(`\{%s\}(.+?)\{/%s\}`, format, format)) - match := formatRegexp.FindStringSubmatch(reply) - giveup := 0 + match = formatRegexp.FindStringSubmatch(reply) + giveup = 0 for len(match) > 0 { giveup++ if giveup > rs.Depth { @@ -336,7 +347,7 @@ func (rs *RiveScript) processTags(username string, message string, reply string, parts := strings.Split(data, "=") if len(parts) > 1 { rs.say("Set uservar %s = %s", parts[0], parts[1]) - rs.users[username].data[parts[0]] = parts[1] + rs.sessions.Set(username, map[string]string{parts[0]: parts[1]}) } else { rs.warn("Malformed tag: %s", match) } @@ -347,18 +358,21 @@ func (rs *RiveScript) processTags(username string, message string, reply string, strValue := parts[1] // Initialize the variable? - if _, ok := rs.users[username].data[name]; !ok { - rs.users[username].data[name] = "0" + var origStr string + origStr, err = rs.sessions.Get(username, name) + if err != nil { + rs.sessions.Set(username, map[string]string{name: "0"}) + origStr = "0" } // Sanity check. value, err := strconv.Atoi(strValue) abort := false if err != nil { - insert = fmt.Sprintf("[ERR: Math can't %s non-numeric value %s]", tag, value) + insert = fmt.Sprintf("[ERR: Math can't %s non-numeric value %s]", tag, strValue) abort = true } - orig, err := strconv.Atoi(rs.users[username].data[name]) + orig, err := strconv.Atoi(origStr) if err != nil { insert = fmt.Sprintf("[ERR: Math can't %s non-numeric user variable %s]", tag, name) abort = true @@ -382,14 +396,13 @@ func (rs *RiveScript) processTags(username string, message string, reply string, if len(insert) == 0 { // Save it to their account. - rs.users[username].data[name] = strconv.Itoa(result) + rs.sessions.Set(username, map[string]string{name: strconv.Itoa(result)}) } } } else if tag == "get" { // user vars - if _, ok := rs.users[username].data[data]; ok { - insert = rs.users[username].data[data] - } else { + insert, err = rs.sessions.Get(username, data) + if err != nil { insert = "undefined" } } else { @@ -415,7 +428,7 @@ func (rs *RiveScript) processTags(username string, message string, reply string, } name := match[1] - rs.users[username].data["topic"] = name + rs.sessions.Set(username, map[string]string{"topic": name}) reply = strings.Replace(reply, fmt.Sprintf("{topic=%s}", name), "", -1) match = re_topic.FindStringSubmatch(reply) } @@ -509,7 +522,7 @@ func (rs *RiveScript) substitute(message string, subs map[string]string, sorted } // Convert the placeholders back in. - tries := 0 + var tries uint for strings.Index(message, "\x00") > -1 { tries++ if tries > rs.Depth { From 2b91b22799a2c45b21f0072c85d422150f2b383a Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Fri, 9 Dec 2016 09:46:16 -0800 Subject: [PATCH 2/4] Update README, make default Config work --- README.md | 34 ++++++++++++++++++++++++++++++-- doc_test.go | 15 +++++++------- lang/javascript/javascript.go | 5 +++-- src/rivescript.go | 37 +++++++++++++++++++---------------- 4 files changed, 63 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3229dc7..763c237 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,12 @@ package main import ( "fmt" - rivescript "github.com/aichaos/rivescript-go" + "github.com/aichaos/rivescript-go" + "github.com/aichaos/rivescript-go/config" ) func main() { - bot := rivescript.New() + bot := rivescript.New(config.Basic()) // Load a directory full of RiveScript documents (.rive files) err := bot.LoadDirectory("eg/brain") @@ -103,6 +104,35 @@ func main() { } ``` +## Configuration + +The constructor takes an optional `Config` struct. Here is a full example with +all the supported options. You only need to provide keys that are different to +the defaults. + +```go +bot := rs.New(&config.Config{ + Debug: false, // Debug mode, off by default + Strict: false, // No strict syntax checking + UTF8: false, // No UTF-8 support enabled by default + Depth: 50, // Becomes default 50 if Depth is <= 0 + SessionManager: memory.New(), // Default in-memory session manager +}) +``` + +For convenience, the `config` package provides two config templates: + +```go +// Basic has all the defaults, plus Strict=true +bot := rs.New(config.Basic()) + +// UTF8 has all of Basic's settings, plus UTF8=true +bot := rs.New(config.UTF8()) + +// You can also provide a nil configuration, which defaults to Basic() +bot := rs.New(nil) +``` + ## Object Macros A common feature in many RiveScript implementations is the object macro, which diff --git a/doc_test.go b/doc_test.go index 1122e9b..7195007 100644 --- a/doc_test.go +++ b/doc_test.go @@ -2,13 +2,15 @@ package rivescript_test import ( "fmt" - rivescript "github.com/aichaos/rivescript-go" - js "github.com/aichaos/rivescript-go/lang/javascript" + + "github.com/aichaos/rivescript-go" + "github.com/aichaos/rivescript-go/config" + "github.com/aichaos/rivescript-go/lang/javascript" rss "github.com/aichaos/rivescript-go/src" ) func ExampleRiveScript() { - bot := rivescript.New() + bot := rivescript.New(config.Basic()) // Load a directory full of RiveScript documents (.rive files) bot.LoadDirectory("eg/brain") @@ -27,11 +29,10 @@ func ExampleRiveScript() { func ExampleRiveScript_javascript() { // Example for configuring the JavaScript object macro handler via Otto. - bot := rivescript.New() + bot := rivescript.New(config.Basic()) // Create the JS handler. - jsHandler := js.New(bot) - bot.SetHandler("javascript", jsHandler) + bot.SetHandler("javascript", javascript.New(bot)) // Now we can use object macros written in JS! bot.Stream(` @@ -66,7 +67,7 @@ func ExampleRiveScript_subroutine() { // Example for defining a Go function as an object macro. // import rss "github.com/aichaos/rivescript-go/src" - bot := rivescript.New() + bot := rivescript.New(config.Basic()) // Define an object macro named `setname` bot.SetSubroutine("setname", func(rs *rss.RiveScript, args []string) string { diff --git a/lang/javascript/javascript.go b/lang/javascript/javascript.go index f0a00a2..f60e39b 100644 --- a/lang/javascript/javascript.go +++ b/lang/javascript/javascript.go @@ -49,9 +49,10 @@ package javascript import ( "fmt" - rivescript "github.com/aichaos/rivescript-go" - "github.com/robertkrimen/otto" "strings" + + "github.com/aichaos/rivescript-go" + "github.com/robertkrimen/otto" ) type JavaScriptHandler struct { diff --git a/src/rivescript.go b/src/rivescript.go index e323eb1..64502a3 100644 --- a/src/rivescript.go +++ b/src/rivescript.go @@ -65,25 +65,28 @@ type RiveScript struct { * Constructor and Debug Methods * ******************************************************************************/ -func New(config *config.Config) *RiveScript { +func New(cfg *config.Config) *RiveScript { rs := new(RiveScript) - if config != nil { - if config.SessionManager == nil { - rs.say("No SessionManager config: using default MemoryStore") - config.SessionManager = memory.New() - } - - if config.Depth <= 0 { - rs.say("No depth config: using default 50") - config.Depth = 50 - } - - rs.Debug = config.Debug - rs.Strict = config.Strict - rs.UTF8 = config.UTF8 - rs.Depth = config.Depth - rs.sessions = config.SessionManager + if cfg == nil { + cfg = config.Basic() } + + if cfg.SessionManager == nil { + rs.say("No SessionManager config: using default MemoryStore") + cfg.SessionManager = memory.New() + } + + if cfg.Depth <= 0 { + rs.say("No depth config: using default 50") + cfg.Depth = 50 + } + + rs.Debug = cfg.Debug + rs.Strict = cfg.Strict + rs.UTF8 = cfg.UTF8 + rs.Depth = cfg.Depth + rs.sessions = cfg.SessionManager + rs.UnicodePunctuation = regexp.MustCompile(`[.,!?;:]`) // Initialize helpers. From 85292d565446d843bbef3a45d1afac06f4938abc Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Fri, 9 Dec 2016 15:55:58 -0800 Subject: [PATCH 3/4] Clean up more documentation --- README.md | 6 ++++-- doc.go | 3 +-- lang/javascript/javascript.go | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 763c237..7804fd5 100644 --- a/README.md +++ b/README.md @@ -166,8 +166,7 @@ string literal to the `RiveScript.SetUnicodePunctuation` function. Example: ```go // Make a new bot with UTF-8 mode enabled. -bot := rivescript.New() -bot.SetUTF8(true) +bot := rivescript.New(config.UTF8()) // Override the punctuation characters that get stripped from the // user's message. @@ -251,6 +250,9 @@ The distributable directory contains only the following types of files: code and returning an "abstract syntax tree." * [rivescript-go/macro](./macro) - Contains an interface for creating your own object macro handlers for foreign programming languages. +* [rivescript-go/sessions](./sessions) - Contains the interface for user + variable session managers as well as the default in-memory manager and the + `NullStore` for testing. ## License diff --git a/doc.go b/doc.go index 275ec4c..4bbd0a7 100644 --- a/doc.go +++ b/doc.go @@ -56,8 +56,7 @@ set being `/[.,!?;:]/g`. This can be overridden by providing a new regexp string literal to the `RiveScript.SetUnicodePunctuation` function. Example: // Make a new bot with UTF-8 mode enabled. - bot := rivescript.New() - bot.SetUTF8(true) + bot := rivescript.New(config.UTF8()) // Override the punctuation characters that get stripped from the // user's message. diff --git a/lang/javascript/javascript.go b/lang/javascript/javascript.go index f60e39b..678eefa 100644 --- a/lang/javascript/javascript.go +++ b/lang/javascript/javascript.go @@ -13,7 +13,7 @@ Usage is simple. In your Golang code: ) func main() { - bot := rivescript.New() + bot := rivescript.New(nil) jsHandler := javascript.New(bot) bot.SetHandler("javascript", jsHandler) From a2415a5cd6077b510fa62549634cd2d0bac46bbe Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 11 Dec 2016 14:49:42 -0800 Subject: [PATCH 4/4] Rename src package --- rivescript.go | 6 +++--- src/astmap.go | 2 +- src/base_test.go | 2 +- src/brain.go | 2 +- src/config.go | 2 +- src/debug.go | 2 +- src/inheritance.go | 2 +- src/loading.go | 2 +- src/parser.go | 2 +- src/regexp.go | 2 +- src/rivescript.go | 2 +- src/rivescript_test.go | 2 +- src/sessions_test.go | 2 +- src/sorting.go | 2 +- src/structs.go | 2 +- src/tags.go | 8 +++++--- src/utils.go | 2 +- 17 files changed, 23 insertions(+), 21 deletions(-) diff --git a/rivescript.go b/rivescript.go index 2899862..40e4230 100644 --- a/rivescript.go +++ b/rivescript.go @@ -22,12 +22,12 @@ import ( const VERSION string = "0.0.3" type RiveScript struct { - rs *src.RiveScript + rs *rivescript.RiveScript } func New(config *config.Config) *RiveScript { bot := new(RiveScript) - bot.rs = src.New(config) + bot.rs = rivescript.New(config) return bot } @@ -169,7 +169,7 @@ Parameters name: The name of your subroutine for the `` tag in RiveScript. fn: A function with a prototype `func(*RiveScript, []string) string` */ -func (self *RiveScript) SetSubroutine(name string, fn src.Subroutine) { +func (self *RiveScript) SetSubroutine(name string, fn rivescript.Subroutine) { self.rs.SetSubroutine(name, fn) } diff --git a/src/astmap.go b/src/astmap.go index fc59351..acd3a51 100644 --- a/src/astmap.go +++ b/src/astmap.go @@ -1,4 +1,4 @@ -package src +package rivescript /* For my own sanity while programming the code, these structs mirror the data diff --git a/src/base_test.go b/src/base_test.go index 3c25812..108aeac 100644 --- a/src/base_test.go +++ b/src/base_test.go @@ -1,4 +1,4 @@ -package src_test +package rivescript_test // NOTE: while these test files live in the 'src' package, they import the // public facing API from the root rivescript-go package. diff --git a/src/brain.go b/src/brain.go index 8775a85..968a7c7 100644 --- a/src/brain.go +++ b/src/brain.go @@ -1,4 +1,4 @@ -package src +package rivescript import ( "fmt" diff --git a/src/config.go b/src/config.go index ac9802e..33a9cba 100644 --- a/src/config.go +++ b/src/config.go @@ -1,4 +1,4 @@ -package src +package rivescript // Public API Configuration Methods diff --git a/src/debug.go b/src/debug.go index cbaa5d7..4ad4fb8 100644 --- a/src/debug.go +++ b/src/debug.go @@ -1,4 +1,4 @@ -package src +package rivescript // Debugging methods diff --git a/src/inheritance.go b/src/inheritance.go index 5f2cb4d..f504e37 100644 --- a/src/inheritance.go +++ b/src/inheritance.go @@ -1,4 +1,4 @@ -package src +package rivescript import "fmt" diff --git a/src/loading.go b/src/loading.go index c18d0aa..e108317 100644 --- a/src/loading.go +++ b/src/loading.go @@ -1,4 +1,4 @@ -package src +package rivescript // Loading and Parsing Methods diff --git a/src/parser.go b/src/parser.go index 8b5040b..f1dafd4 100644 --- a/src/parser.go +++ b/src/parser.go @@ -1,4 +1,4 @@ -package src +package rivescript // parse loads the RiveScript code into the bot's memory. func (rs *RiveScript) parse(path string, lines []string) error { diff --git a/src/regexp.go b/src/regexp.go index df577b2..0740f69 100644 --- a/src/regexp.go +++ b/src/regexp.go @@ -1,4 +1,4 @@ -package src +package rivescript // Common regular expressions. diff --git a/src/rivescript.go b/src/rivescript.go index 64502a3..b67b117 100644 --- a/src/rivescript.go +++ b/src/rivescript.go @@ -16,7 +16,7 @@ this package lest you be tempted to use it (don't). You've been warned. Here be dragons. */ -package src +package rivescript import ( "regexp" diff --git a/src/rivescript_test.go b/src/rivescript_test.go index 1135e05..6f2a84e 100644 --- a/src/rivescript_test.go +++ b/src/rivescript_test.go @@ -1,4 +1,4 @@ -package src_test +package rivescript_test import ( "fmt" diff --git a/src/sessions_test.go b/src/sessions_test.go index 8991592..bd826ce 100644 --- a/src/sessions_test.go +++ b/src/sessions_test.go @@ -1,4 +1,4 @@ -package src_test +package rivescript_test import ( "testing" diff --git a/src/sorting.go b/src/sorting.go index 40f8ddf..a0b0d4d 100644 --- a/src/sorting.go +++ b/src/sorting.go @@ -1,4 +1,4 @@ -package src +package rivescript // Data sorting functions diff --git a/src/structs.go b/src/structs.go index 7c52c92..bd57c22 100644 --- a/src/structs.go +++ b/src/structs.go @@ -1,4 +1,4 @@ -package src +package rivescript // Miscellaneous structures diff --git a/src/tags.go b/src/tags.go index 588fcb8..466f409 100644 --- a/src/tags.go +++ b/src/tags.go @@ -1,4 +1,4 @@ -package src +package rivescript // Tag processing functions. @@ -366,13 +366,15 @@ func (rs *RiveScript) processTags(username string, message string, reply string, } // Sanity check. - value, err := strconv.Atoi(strValue) + var value int + value, err = strconv.Atoi(strValue) abort := false if err != nil { insert = fmt.Sprintf("[ERR: Math can't %s non-numeric value %s]", tag, strValue) abort = true } - orig, err := strconv.Atoi(origStr) + var orig int + orig, err = strconv.Atoi(origStr) if err != nil { insert = fmt.Sprintf("[ERR: Math can't %s non-numeric user variable %s]", tag, name) abort = true diff --git a/src/utils.go b/src/utils.go index 824f5bc..9fccdfe 100644 --- a/src/utils.go +++ b/src/utils.go @@ -1,4 +1,4 @@ -package src +package rivescript // Miscellaneous utility functions.