Kaynağa Gözat

Added a CLI command hierarchy using spf13/cobra. Storage WIP.

Frederic G. MARAND 9 yıl önce
ebeveyn
işleme
a142e6531a

+ 56 - 0
command/command.go

@@ -0,0 +1,56 @@
+/*
+The command package provides the command structure for kurz.
+
+Each command is implemented in its own file: the main command.go file only
+contains information for the root command, its flags, and helpers.
+
+Command files are expected to contain the following, for a command "foo"
+
+	- the command itself, as a *command.Cobra instance, named FooCommand
+	- the command flag variables, named fooSomething. Always prefix by the command name to avoid name conflicts
+	- a init() function which will bind the flags to the command, and the command itself to other commands.
+	- optional:
+	  - for commands usable without a subcommand, the command callable, as fooCommand(c *cobra.Command, args []string).
+	  - commands needing subcommands do not need a callable
+
+First-level commands bind to kurz, other commands may bind to the other FooCommand variables.
+*/
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+	"os"
+)
+
+var kurz = &cobra.Command{
+	Use:   "kurz",
+	Short: "kurz CLI",
+	Long:  "The kurz command uses the kurz API to provide a web-based URL shortener/aliaser",
+}
+
+var Verbose bool
+
+const ConfigName = ".kurz.js"
+
+func verbose() rune {
+	if Verbose {
+		return 'Y'
+	} else {
+		return 'N'
+	}
+}
+
+/*
+Build() assembles the command structure from the various command files.
+It is the only function the main package needs to be aware of.
+*/
+func Build() {
+	kurz.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "Be verbose")
+
+	err := kurz.Execute()
+	if err != nil {
+		fmt.Print("Error initializing commands: %+v\n", err)
+		os.Exit(1)
+	}
+}

+ 19 - 0
command/export.go

@@ -0,0 +1,19 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var ExportCommand = &cobra.Command{
+	Use:   "export",
+	Short: "Export kurz data",
+	Long:  fmt.Printf("Export the kurz configuration (from ~/%s) or content", ConfigName),
+}
+
+var exportDestination string
+
+func init() {
+	ExportCommand.Flags().StringVarP(&exportDestination, "output", "o", "", "The file in which the output will be written")
+	kurz.AddCommand(ExportCommand)
+}

+ 21 - 0
command/export_config.go

@@ -0,0 +1,21 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var ExportConfigCommand = &cobra.Command{
+	Use:   "config",
+	Short: "Export kurz configuration",
+	Long:  fmt.Sprintf("Export the kurz configuration (from ~/%s)", ConfigName),
+	Run:   exportConfigCommand,
+}
+
+func exportConfigCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to export the kurz configuration.")
+}
+
+func init() {
+	ExportCommand.AddCommand(ExportConfigCommand)
+}

+ 21 - 0
command/export_content.go

@@ -0,0 +1,21 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var ExportContentCommand = &cobra.Command{
+	Use:   "content",
+	Short: "Export kurz content",
+	Long:  "Export the kurz content from the database",
+	Run:   exportContentCommand,
+}
+
+func exportContentCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to export the kurz configuration.")
+}
+
+func init() {
+	ExportCommand.AddCommand(ExportContentCommand)
+}

+ 21 - 0
command/install.go

@@ -0,0 +1,21 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var InstallCommand = &cobra.Command{
+	Use:   "install",
+	Short: "Install kurz",
+	Long:  "Install kurz completely",
+	Run:   installCommand,
+}
+
+func installCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to install kurz completely.")
+}
+
+func init() {
+	kurz.AddCommand(InstallCommand)
+}

+ 21 - 0
command/install_config.go

@@ -0,0 +1,21 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var InstallConfigCommand = &cobra.Command{
+	Use:   "config",
+	Short: "Install configuration",
+	Long:  fmt.Printf("Install the kurz configuration to ~/%s", ConfigName),
+	Run:   installConfigCommand,
+}
+
+func installConfigCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to install the kurz configuration.")
+}
+
+func init() {
+	InstallCommand.AddCommand(InstallConfigCommand)
+}

+ 21 - 0
command/install_schema.go

@@ -0,0 +1,21 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var InstallSchemaCommand = &cobra.Command{
+	Use:   "schema",
+	Short: "Install schema",
+	Long:  "Install the kurz database schema",
+	Run:   installSchemaCommand,
+}
+
+func installSchemaCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to install the kurz database schema.")
+}
+
+func init() {
+	InstallCommand.AddCommand(InstallSchemaCommand)
+}

+ 24 - 0
command/serve.go

@@ -0,0 +1,24 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var ServeCommand = &cobra.Command{
+	Use:   "server",
+	Short: "Run the kurz server",
+	Long:  fmt.Printf("Run the kurz server, using the configuration in ~/%s", ConfigName),
+	Run:   serveCommand,
+}
+
+func serveCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to serve kurz.")
+}
+
+var servePort int
+
+func init() {
+	ServeCommand.Flags().IntVarP(&servePort, "port", "p", 80, "The IP port on which to listen")
+	kurz.AddCommand(ServeCommand)
+}

+ 21 - 0
command/uninstall.go

@@ -0,0 +1,21 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var UninstallCommand = &cobra.Command{
+	Use:   "uninstall",
+	Short: "Uninstall kurz",
+	Long:  "Uninstall kurz completely",
+	Run:   uninstallCommand,
+}
+
+func uninstallCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to uninstall kurz completely.")
+}
+
+func init() {
+	kurz.AddCommand(UninstallCommand)
+}

+ 21 - 0
command/uninstall_config.go

@@ -0,0 +1,21 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var UninstallConfigCommand = &cobra.Command{
+	Use:   "config",
+	Short: "Uninstall configuration",
+	Long:  fmt.Printf("Uninstall the kurz configuration from ~/%s", ConfigName),
+	Run:   uninstallConfigCommand,
+}
+
+func uninstallConfigCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to uninstall the kurz configuration.")
+}
+
+func init() {
+	UninstallCommand.AddCommand(UninstallConfigCommand)
+}

+ 21 - 0
command/uninstall_schema.go

@@ -0,0 +1,21 @@
+package command
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+var UninstallSchemaCommand = &cobra.Command{
+	Use:   "schema",
+	Short: "Uninstall schema",
+	Long:  "Uninstall the kurz database schema",
+	Run:   uninstallSchemaCommand,
+}
+
+func uninstallSchemaCommand(c *cobra.Command, args []string) {
+	fmt.Println("Pretend to uninstall the kurz database schema.")
+}
+
+func init() {
+	UninstallCommand.AddCommand(UninstallSchemaCommand)
+}

+ 3 - 0
default.kurz.js

@@ -0,0 +1,3 @@
+{
+  "port": 80
+}

+ 106 - 0
doc/entites.md

@@ -0,0 +1,106 @@
+		
+Model entities implement the EntityInterface, which combines:
+
+- the StorableEntityInterface, which allows them to call upon
+	- a storage controller, implementing the StorageControllerInterface
+
+
+	StorableEntityInterface
+		StorageController() StorageControllerInterface
+
+The storage controllers implement the StorageControllerInterface, which defines:
+
+- the collection holding the data
+- the data id: the name of the possibly multiple data keys to combine to identify an entity
+- the driver to use to access the data
+
+
+	StorageControllerInterface
+		Collection() string
+		Id() []string
+		Driver() *StorageDriverInterface
+
+The storage drivers implement the StorageDriver interface, which defines:
+
+- the underlying opaque go-level object
+- database enumeration
+
+The storage driver instances are likely to be shared objects, hence may not contain
+any per-controller or per-entity information, and cannot be embedded in storage
+controllers, but must be standalone
+
+	StorageDriverInterface
+		DbDriver() interface{}
+		Databases() []DatabaseInterface
+		DropDatabase(db DatabaseInterface)		
+		MakeDatabase() DatabaseInterface
+
+Storage drivers may exist in multiple instances, to address multiple connections
+to different DB servers. Multiple driver instance MUST NOT address the same server,
+to avoid consistency issues.
+
+Databases implement the DatabaseInterface, which defines:
+
+- collection enumeration
+- collection access by name
+- collection deletion
+
+	DatabaseInterface
+		Collections() []string
+		Collection(name) CollectionInterface
+		DropCollection(name string)
+
+Collections implement the Collection interface, which defines:
+
+- document (row) CRUD
+- truncation
+
+	CollectionInterface
+		Truncate()
+		Document(id IdInterface)
+		Find(crit CriteriumInterface) QueryInterface
+
+Criteria implement the CriteriumInterface
+
+	CriteriumInterface
+		Fields() []string
+
+
+Queries are promises implementing the QueryInterface. They are meant to be used as a fluent interface,
+like: cursor, err := coll.Find(someCriteria).Order(someOrdering).Execute()
+
+	QueryInterface
+		Execute() ExecutedQueryInterface
+		Order([]OrderSpecification) QueryInterface
+		Skip(offset int64) QueryInterface
+		Limit(count int64) QueryInterface
+		Count() int64
+		Fields() []string
+
+Note that Fields() will only return the fields specifically requested. If the query
+does not specify fields (like SQL "SELECT *" or MongoDB "coll.find({})", it will
+return empty.
+
+Executed queries implement the ExecutedQueryInterface. They provide access to the
+non-executed query, a cursor, and the fields available in all documents (which)
+may need cursor unrolling and be very costly)
+
+	ExecutedQueryInterface
+		Cursor() CursorInterface
+		Query() QueryInterface
+		Fields() []string
+
+For selection queries returning multiple documents, the cursor will typically be iterated.
+Selection queries returning a single document are just a case of 1 being a value of n.
+Count queries and modification queries return a cursor to available data, like 
+count, affected documents or last insert id. The available columns depend on the query 
+type, hence the use of Fields().
+
+Every database driver provides support for queries which this set of interfaces
+cannot build. Applications needing to build such queries, can use DbDriver() to
+access the database driver for their custom needs, losing database-independence.
+
+They can limit the db-dependent part by building an ExecutedQueryInterface from
+their results, at which point code can resume db independence. Bridge packages 
+can provide this mapping.
+

+ 32 - 11
kurz.go

@@ -1,18 +1,39 @@
+/*
+Kurz URl shortener app
+
+kurz
+	-v														Verbose (default: false).
+	help													Display help (cobra builtin).
+	server 												Serve kurz.
+		-p|--port <port_id>					Serve on this IP port (default: 80).
+	install												Install kurz completely.
+		config											Install the configuration define by command line to ~.
+		schema											Install the kurz database schema.
+	export												Export kurz data.
+		-o <file>										Specify a destination file.
+		config								  		Export kurz configuration.
+		content											Export kurz content.
+	uninstall											Uninstall kurz completely.
+		config											Uninstall the curz configuration file from ~.
+		schema											Uninstall the curz database schema.
+*/
 package main
 
 import (
-	"fmt"
-	"github.com/FGM/kurz/storage"
-	"github.com/FGM/kurz/strategy"
-	"log"
+	//	"fmt"
+	"github.com/FGM/kurz/command"
+	//	"github.com/FGM/kurz/storage"
+	//	"github.com/FGM/kurz/strategy"
+	//	"log"
 )
 
 func main() {
-	err := storage.Service.Open()
-	if err != nil {
-		log.Fatal(err)
-	}
-	defer storage.Service.Close()
-
-	fmt.Print(strategy.Statistics.Refresh(storage.Service))
+	command.Build()
+	//	err := storage.Service.Open()
+	//	if err != nil {
+	//		log.Fatal(err)
+	//	}
+	//	defer storage.Service.Close()
+	//
+	//	fmt.Print(strategy.Statistics.Refresh(storage.Service))
 }

+ 5 - 3
storage/info.go

@@ -12,7 +12,8 @@ type EventInfo struct {
 	ts     time.Time
 }
 
-type EventInfoStorage struct {
+type EventInfoStorageController struct {
+	controller StorageControllerInterface
 }
 
 func (eis EventInfoStorage) Table() string {
@@ -21,7 +22,7 @@ func (eis EventInfoStorage) Table() string {
 
 var EventInfoService EventInfoStorage = EventInfoStorage{}
 
-func EventInfoLoadFromId(id int64, storage Storage) (EventInfo, error) {
+func (sc EventInfoStorageController) EventInfoLoadFromId(id int64) (EventInfo, error) {
 	var ei EventInfo
 	var source string
 	var ip string
@@ -33,7 +34,8 @@ FROM eventinfo
 WHERE id = ?
 	`
 
-	err := storage.DB.QueryRow(sql, id).Scan(&source, &ip, &ts)
+	sc.controller.Table()
+	err := sc.controller.Storage().DB.QueryRow(sql, id).Scan(&source, &ip, &ts)
 	if err != nil {
 		ei = EventInfo{
 			Id:     id,

+ 19 - 1
storage/storage.go

@@ -22,6 +22,10 @@ import (
 	"log"
 )
 
+type StorageInterface interface {
+	Controller() StorageControllerInterface
+}
+
 type Storage struct {
 	DB           *sql.DB
 	DSN          string
@@ -79,10 +83,24 @@ func (s *Storage) SetDSN(dsn string) {
 	s.DSN = dsn
 }
 
-type StorageController interface {
+type StorageControllerInterface interface {
+	Storage() StorageInterface
 	Table() string
 }
 
+type StorageController struct {
+	StorageInstance StorageInterface
+	table           string
+}
+
+func (sc StorageController) Storage() StorageInterface {
+	return nil
+}
+
+func (sc StorageController) Table() string {
+	return sc.table
+}
+
 /*
 init() initializes the storage information from the command-line flag "dsn".
 */