inital working metl
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
metl.exe
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
9
.idea/metl.iml
generated
Normal file
9
.idea/metl.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/metl.iml" filepath="$PROJECT_DIR$/.idea/metl.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
29
.idea/watcherTasks.xml
generated
Normal file
29
.idea/watcherTasks.xml
generated
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectTasksOptions">
|
||||
<TaskOptions isEnabled="true">
|
||||
<option name="arguments" value="fmt $FilePath$" />
|
||||
<option name="checkSyntaxErrors" value="true" />
|
||||
<option name="description" />
|
||||
<option name="exitCodeBehavior" value="ERROR" />
|
||||
<option name="fileExtension" value="go" />
|
||||
<option name="immediateSync" value="false" />
|
||||
<option name="name" value="go fmt" />
|
||||
<option name="output" value="$FilePath$" />
|
||||
<option name="outputFilters">
|
||||
<array />
|
||||
</option>
|
||||
<option name="outputFromStdout" value="false" />
|
||||
<option name="program" value="$GoExecPath$" />
|
||||
<option name="runOnExternalChanges" value="false" />
|
||||
<option name="scopeName" value="Project Files" />
|
||||
<option name="trackOnlyRoot" value="true" />
|
||||
<option name="workingDir" value="$ProjectFileDir$" />
|
||||
<envs>
|
||||
<env name="GOROOT" value="$GOROOT$" />
|
||||
<env name="GOPATH" value="$GOPATH$" />
|
||||
<env name="PATH" value="$GoBinDirs$" />
|
||||
</envs>
|
||||
</TaskOptions>
|
||||
</component>
|
||||
</project>
|
102
action-modifycolumn.go
Normal file
102
action-modifycolumn.go
Normal file
@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ActionModifyColumn struct {
|
||||
ActionID uuid.UUID
|
||||
OutputSchema *Schema
|
||||
InputSchema *Schema
|
||||
Config ModifyColumnConfig
|
||||
status ActionStatus
|
||||
bus *Bus
|
||||
}
|
||||
|
||||
type ModifyColumnConfig struct {
|
||||
ActionConfig
|
||||
}
|
||||
|
||||
func NewModifyColumn(config ModifyColumnConfig, b *Bus) (ActionModifyColumn, error) {
|
||||
as := ActionModifyColumn{}
|
||||
err := as.init(config, b)
|
||||
if err != nil {
|
||||
return ActionModifyColumn{}, err
|
||||
}
|
||||
return as, nil
|
||||
}
|
||||
|
||||
func (s *ActionModifyColumn) init(config ModifyColumnConfig, b *Bus) error {
|
||||
if len(config.Inputs) <= 0 {
|
||||
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", config.ActionID.String()))
|
||||
}
|
||||
s.InputSchema = config.Inputs[0].InputSchema
|
||||
s.OutputSchema = config.OutputSchema
|
||||
s.ActionID = config.ActionID
|
||||
s.Config = config
|
||||
s.status = ActionStatus{
|
||||
State: NotStarted,
|
||||
HandledRows: 0,
|
||||
ExpectedRows: -1,
|
||||
}
|
||||
s.bus = b
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionModifyColumn) Run(contentRow ContentRow) error {
|
||||
|
||||
// convert the schema to the new one
|
||||
newContentRow := ContentRow{
|
||||
Schema: s.Config.OutputSchema,
|
||||
Values: make(map[uuid.UUID]string),
|
||||
Raw: "",
|
||||
Source: &s.ActionID,
|
||||
}
|
||||
|
||||
for _, col := range newContentRow.Schema.Columns {
|
||||
value, ok := contentRow.Values[col.ID]
|
||||
if !ok {
|
||||
value = col.DefaultValue
|
||||
}
|
||||
newContentRow.Values[col.ID] = value
|
||||
if strings.EqualFold(newContentRow.Raw, "") {
|
||||
newContentRow.Raw = value
|
||||
} else {
|
||||
newContentRow.Raw = newContentRow.Raw + "," + value
|
||||
}
|
||||
}
|
||||
return s.publishRow(newContentRow)
|
||||
}
|
||||
|
||||
func (s *ActionModifyColumn) publishRow(row ContentRow) error {
|
||||
// TODO: decide on the method of transporting data to the next action
|
||||
return s.bus.Run(s.ActionID, row)
|
||||
}
|
||||
|
||||
func (s *ActionModifyColumn) publishErrorRow(row ContentRow) error {
|
||||
// TODO: decide on the method of transporting data to the next action
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionModifyColumn) Validate() error {
|
||||
if len(s.Config.Inputs) <= 0 {
|
||||
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionModifyColumn) GetActionID() uuid.UUID {
|
||||
return s.ActionID
|
||||
}
|
||||
|
||||
func (s *ActionModifyColumn) Status() ActionStatus {
|
||||
return s.status
|
||||
}
|
||||
|
||||
func (s *ActionModifyColumn) GetActionConfig() ActionConfig {
|
||||
return s.Config.ActionConfig
|
||||
}
|
122
action-stdin.go
Normal file
122
action-stdin.go
Normal file
@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ActionStdIn struct {
|
||||
ActionID uuid.UUID
|
||||
OutputSchema *Schema
|
||||
Config StdInConfig
|
||||
status ActionStatus
|
||||
bus *Bus
|
||||
}
|
||||
|
||||
type StdInConfig struct {
|
||||
ActionConfig
|
||||
HasHeader bool
|
||||
}
|
||||
|
||||
func NewStdIn(config StdInConfig, b *Bus) (ActionStdIn, error) {
|
||||
as := ActionStdIn{}
|
||||
err := as.init(config, b)
|
||||
if err != nil {
|
||||
return ActionStdIn{}, err
|
||||
}
|
||||
return as, nil
|
||||
}
|
||||
|
||||
func (s *ActionStdIn) init(config StdInConfig, b *Bus) error {
|
||||
s.OutputSchema = config.OutputSchema
|
||||
s.ActionID = config.ActionID
|
||||
s.Config = config
|
||||
s.status = ActionStatus{
|
||||
State: NotStarted,
|
||||
HandledRows: 0,
|
||||
ExpectedRows: -1,
|
||||
}
|
||||
s.bus = b
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionStdIn) Run(_ ContentRow) error {
|
||||
s.status.State = Running
|
||||
rowsProcessed := int64(0)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
potentialRow := scanner.Text()
|
||||
// TODO: handle headers
|
||||
if rowsProcessed <= 0 && s.Config.HasHeader {
|
||||
// currently ignoring the first row always
|
||||
rowsProcessed = rowsProcessed + 1
|
||||
continue
|
||||
}
|
||||
potentialColumns := strings.Split(potentialRow, ",")
|
||||
contentRow := ContentRow{
|
||||
Schema: s.OutputSchema,
|
||||
Values: make(map[uuid.UUID]string),
|
||||
Raw: potentialRow,
|
||||
Source: &s.ActionID,
|
||||
}
|
||||
rowValid := true
|
||||
for i, c := range potentialColumns {
|
||||
value := strings.TrimSpace(c)
|
||||
valid, err := ValidateDataType(value, s.OutputSchema.Columns[i].Type)
|
||||
if err != nil {
|
||||
rowValid = false
|
||||
}
|
||||
rowValid = rowValid && valid
|
||||
contentRow.Values[s.OutputSchema.Columns[i].ID] = value
|
||||
}
|
||||
if !rowValid {
|
||||
_ = s.publishErrorRow(contentRow)
|
||||
}
|
||||
err := s.publishRow(contentRow)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("error publishing row. ActionID: %v | %v", s.ActionID, err.Error()))
|
||||
}
|
||||
rowsProcessed = rowsProcessed + 1
|
||||
s.status.HandledRows = rowsProcessed
|
||||
}
|
||||
s.status.State = Finished
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return errors.New(fmt.Sprintf("error scanning input. ActionID: %v | %v", s.ActionID, err.Error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionStdIn) publishRow(row ContentRow) error {
|
||||
// TODO: decide on the method of transporting data to the next action
|
||||
return s.bus.Run(s.ActionID, row)
|
||||
}
|
||||
|
||||
func (s *ActionStdIn) publishErrorRow(row ContentRow) error {
|
||||
// TODO: decide on the method of transporting data to the next action
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionStdIn) Validate() error {
|
||||
if len(s.Config.Inputs) <= 0 {
|
||||
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionStdIn) GetActionID() uuid.UUID {
|
||||
return s.ActionID
|
||||
}
|
||||
|
||||
func (s *ActionStdIn) Status() ActionStatus {
|
||||
return s.status
|
||||
}
|
||||
|
||||
func (s *ActionStdIn) GetActionConfig() ActionConfig {
|
||||
return s.Config.ActionConfig
|
||||
}
|
99
action-stdout.go
Normal file
99
action-stdout.go
Normal file
@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ActionStdOut struct {
|
||||
ActionID uuid.UUID
|
||||
OutputSchema *Schema
|
||||
InputSchema *Schema
|
||||
Config StdOutConfig
|
||||
status ActionStatus
|
||||
bus *Bus
|
||||
}
|
||||
|
||||
type StdOutConfig struct {
|
||||
ActionConfig
|
||||
AddHeader bool
|
||||
}
|
||||
|
||||
func NewStdOut(config StdOutConfig, b *Bus) (ActionStdOut, error) {
|
||||
as := ActionStdOut{}
|
||||
err := as.init(config, b)
|
||||
if err != nil {
|
||||
return ActionStdOut{}, err
|
||||
}
|
||||
return as, nil
|
||||
}
|
||||
|
||||
func (s *ActionStdOut) init(config StdOutConfig, b *Bus) error {
|
||||
if len(config.Inputs) <= 0 {
|
||||
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", config.ActionID.String()))
|
||||
}
|
||||
s.InputSchema = config.Inputs[0].InputSchema
|
||||
s.OutputSchema = config.OutputSchema
|
||||
s.ActionID = config.ActionID
|
||||
s.Config = config
|
||||
s.status = ActionStatus{
|
||||
State: NotStarted,
|
||||
HandledRows: 0,
|
||||
ExpectedRows: -1,
|
||||
}
|
||||
s.bus = b
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionStdOut) Run(contentRow ContentRow) error {
|
||||
s.status.State = Running
|
||||
if s.status.HandledRows == 0 && s.Config.AddHeader {
|
||||
headerRowRaw := ""
|
||||
for _, h := range s.Config.OutputSchema.Columns {
|
||||
if strings.EqualFold(headerRowRaw, "") {
|
||||
headerRowRaw = h.Name
|
||||
} else {
|
||||
headerRowRaw = headerRowRaw + "," + h.Name
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v\n", headerRowRaw)
|
||||
}
|
||||
fmt.Printf("%v\n", contentRow.Raw)
|
||||
s.status.HandledRows = s.status.HandledRows + 1
|
||||
if s.status.HandledRows == s.status.ExpectedRows {
|
||||
s.status.State = Finished
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionStdOut) publishRow(row ContentRow) error {
|
||||
// TODO: decide on the method of transporting data to the next action
|
||||
return s.bus.Run(s.ActionID, row)
|
||||
}
|
||||
|
||||
func (s *ActionStdOut) publishErrorRow(row ContentRow) error {
|
||||
// TODO: decide on the method of transporting data to the next action
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionStdOut) Validate() error {
|
||||
if len(s.Config.Inputs) <= 0 {
|
||||
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ActionStdOut) GetActionID() uuid.UUID {
|
||||
return s.ActionID
|
||||
}
|
||||
|
||||
func (s *ActionStdOut) Status() ActionStatus {
|
||||
return s.status
|
||||
}
|
||||
|
||||
func (s *ActionStdOut) GetActionConfig() ActionConfig {
|
||||
return s.Config.ActionConfig
|
||||
}
|
247
action.go
Normal file
247
action.go
Normal file
@ -0,0 +1,247 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Action interface {
|
||||
Run(row ContentRow) error
|
||||
//publishRow(row ContentRow) error
|
||||
//publishErrorRow(row ContentRow) error
|
||||
//Start()
|
||||
//Finish()
|
||||
Status() ActionStatus
|
||||
Validate() error
|
||||
GetActionID() uuid.UUID
|
||||
GetActionConfig() ActionConfig
|
||||
}
|
||||
|
||||
type Schema struct {
|
||||
Columns []ColumnDefinition
|
||||
}
|
||||
|
||||
type ColumnDefinition struct {
|
||||
Name string
|
||||
ID uuid.UUID
|
||||
Type DataType
|
||||
DefaultValue string
|
||||
}
|
||||
|
||||
type ContentRow struct {
|
||||
Schema *Schema
|
||||
Values map[uuid.UUID]string
|
||||
Raw string
|
||||
Source *uuid.UUID
|
||||
}
|
||||
|
||||
type ActionConfig struct {
|
||||
ActionID uuid.UUID
|
||||
Type ActionType
|
||||
OutputSchema *Schema
|
||||
Inputs []InputDefinition
|
||||
}
|
||||
|
||||
type InputDefinition struct {
|
||||
InputSchema *Schema
|
||||
ActionID *uuid.UUID
|
||||
// TODO: decide if input types exist
|
||||
}
|
||||
|
||||
type ActionStatus struct {
|
||||
State StateValue
|
||||
ExpectedRows int64
|
||||
HandledRows int64
|
||||
}
|
||||
|
||||
// DataType - Custom type to hold value for data type ranging from 1-5
|
||||
type DataType int
|
||||
|
||||
// Declare related constants for each datatype starting with index 1
|
||||
const (
|
||||
String DataType = iota + 1
|
||||
Integer
|
||||
Float
|
||||
Date
|
||||
DateTime
|
||||
)
|
||||
|
||||
// String - Creating common behavior give the type a String function
|
||||
func (d DataType) String() string {
|
||||
return [...]string{"String", "Integer", "Float", "Date", "DateTime"}[d-1]
|
||||
}
|
||||
|
||||
// EnumIndex - Creating common behavior give the type a EnumIndex function
|
||||
func (d DataType) EnumIndex() int {
|
||||
return int(d)
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the enum as a quoted json string
|
||||
func (s DataType) MarshalJSON() ([]byte, error) {
|
||||
buffer := bytes.NewBufferString(`"`)
|
||||
buffer.WriteString(s.String())
|
||||
buffer.WriteString(`"`)
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmashals a quoted json string to the enum value
|
||||
func (s *DataType) UnmarshalJSON(b []byte) error {
|
||||
var j string
|
||||
err := json.Unmarshal(b, &j)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Note that if the string cannot be found then it will be set to the zero value, 'String' in this case.
|
||||
var toID = map[string]DataType{
|
||||
"String": String,
|
||||
"Integer": Integer,
|
||||
"Float": Float,
|
||||
"Date": Date,
|
||||
"DateTime": DateTime,
|
||||
}
|
||||
*s = toID[j]
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateDataType(in string, dataType DataType) (bool, error) {
|
||||
switch dataType {
|
||||
case String:
|
||||
return ValidStringDataType(in), nil
|
||||
case Integer:
|
||||
return ValidIntegerDataType(in)
|
||||
case Float:
|
||||
return ValidFloatDataType(in)
|
||||
case Date:
|
||||
return ValidDateDataType(in)
|
||||
case DateTime:
|
||||
return ValidDateTimeDataType(in)
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ValidStringDataType(in string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func ValidIntegerDataType(in string) (bool, error) {
|
||||
i, err := strconv.ParseInt(in, 10, 64)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.EqualFold(in, fmt.Sprintf("%v", i)), nil
|
||||
}
|
||||
|
||||
func ValidFloatDataType(in string) (bool, error) {
|
||||
i, err := strconv.ParseFloat(in, 64)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.EqualFold(in, fmt.Sprintf("%v", i)), nil
|
||||
}
|
||||
|
||||
func ValidDateDataType(in string) (bool, error) {
|
||||
// TODO: implement
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func ValidDateTimeDataType(in string) (bool, error) {
|
||||
// TODO: implement
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ActionType - Custom type to hold value for data type ranging from 1-5
|
||||
type ActionType int
|
||||
|
||||
const (
|
||||
StdIn ActionType = iota + 1
|
||||
ModifyColumn
|
||||
StdOut
|
||||
)
|
||||
|
||||
// String - Creating common behavior give the type a String function
|
||||
func (d ActionType) String() string {
|
||||
return [...]string{"StdIn", "ModifyColumn", "StdOut"}[d-1]
|
||||
}
|
||||
|
||||
// EnumIndex - Creating common behavior give the type a EnumIndex function
|
||||
func (d ActionType) EnumIndex() int {
|
||||
return int(d)
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the enum as a quoted json string
|
||||
func (s ActionType) MarshalJSON() ([]byte, error) {
|
||||
buffer := bytes.NewBufferString(`"`)
|
||||
buffer.WriteString(s.String())
|
||||
buffer.WriteString(`"`)
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmashals a quoted json string to the enum value
|
||||
func (s *ActionType) UnmarshalJSON(b []byte) error {
|
||||
var j string
|
||||
err := json.Unmarshal(b, &j)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Note that if the string cannot be found then it will be set to the zero value, 'String' in this case.
|
||||
var toID = map[string]ActionType{
|
||||
"StdIn": StdIn,
|
||||
"ModifyColumn": ModifyColumn,
|
||||
"StdOut": StdOut,
|
||||
}
|
||||
*s = toID[j]
|
||||
return nil
|
||||
}
|
||||
|
||||
// StateValue - Custom type to hold value for data type ranging from 1-5
|
||||
type StateValue int
|
||||
|
||||
// Declare related constants for each datatype starting with index 1
|
||||
const (
|
||||
NotStarted StateValue = iota + 1
|
||||
Paused
|
||||
Running
|
||||
Finished
|
||||
)
|
||||
|
||||
// String - Creating common behavior give the type a String function
|
||||
func (d StateValue) String() string {
|
||||
return [...]string{"NotStarted", "Paused", "Running", "Finished"}[d-1]
|
||||
}
|
||||
|
||||
// EnumIndex - Creating common behavior give the type a EnumIndex function
|
||||
func (d StateValue) EnumIndex() int {
|
||||
return int(d)
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the enum as a quoted json string
|
||||
func (s StateValue) MarshalJSON() ([]byte, error) {
|
||||
buffer := bytes.NewBufferString(`"`)
|
||||
buffer.WriteString(s.String())
|
||||
buffer.WriteString(`"`)
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmashals a quoted json string to the enum value
|
||||
func (s *StateValue) UnmarshalJSON(b []byte) error {
|
||||
var j string
|
||||
err := json.Unmarshal(b, &j)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Note that if the string cannot be found then it will be set to the zero value, 'String' in this case.
|
||||
var toID = map[string]StateValue{
|
||||
"NotStarted": NotStarted,
|
||||
"Paused": Paused,
|
||||
"Running": Running,
|
||||
"Finished": Finished,
|
||||
}
|
||||
*s = toID[j]
|
||||
return nil
|
||||
}
|
154
bus.go
Normal file
154
bus.go
Normal file
@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type MetlConfig struct {
|
||||
ActionConfigs []parallelActionConfig `json:"actionConfigs"`
|
||||
}
|
||||
|
||||
type parallelActionConfig struct {
|
||||
ActionConfig
|
||||
StdInConfig
|
||||
ModifyColumnConfig
|
||||
StdOutConfig
|
||||
}
|
||||
|
||||
type Bus struct {
|
||||
actions map[uuid.UUID]Action
|
||||
events map[uuid.UUID][]func(contentRow ContentRow) error
|
||||
}
|
||||
|
||||
func NewBus(config MetlConfig) (Bus, error) {
|
||||
b := Bus{}
|
||||
err := b.init(config)
|
||||
if err != nil {
|
||||
return Bus{}, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func wrap(err error, message string) error {
|
||||
return errors.New(fmt.Sprintf("%v | %v", message, err.Error()))
|
||||
}
|
||||
|
||||
func (b *Bus) init(config MetlConfig) error {
|
||||
b.events = make(map[uuid.UUID][]func(contentRow ContentRow) error)
|
||||
b.actions = make(map[uuid.UUID]Action)
|
||||
for _, ac := range config.ActionConfigs {
|
||||
// setup the event list for this action (it will contain all the Run funcs for actions that depend on this action
|
||||
_, ok := b.events[ac.ActionID]
|
||||
if !ok {
|
||||
b.events[ac.ActionID] = make([]func(contentRow ContentRow) error, 0)
|
||||
}
|
||||
|
||||
var action Action
|
||||
switch ac.Type {
|
||||
case StdIn:
|
||||
stdIn, err := NewStdIn(StdInConfig{
|
||||
ActionConfig: ac.ActionConfig,
|
||||
HasHeader: ac.HasHeader,
|
||||
}, b)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("error initializing StdIn Action. ActionID: %v | %v", ac.ActionID, err.Error()))
|
||||
}
|
||||
action = &stdIn
|
||||
case ModifyColumn:
|
||||
modCol, err := NewModifyColumn(ModifyColumnConfig{
|
||||
ActionConfig: ac.ActionConfig,
|
||||
}, b)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("error initializing ModifyColumn Action. ActionID: %v | %v", ac.ActionID, err.Error()))
|
||||
}
|
||||
action = &modCol
|
||||
case StdOut:
|
||||
stdOut, err := NewStdOut(StdOutConfig{
|
||||
ActionConfig: ac.ActionConfig,
|
||||
AddHeader: ac.AddHeader,
|
||||
}, b)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("error initializing StdOut Action. ActionID: %v | %v", ac.ActionID, err.Error()))
|
||||
}
|
||||
action = &stdOut
|
||||
// TODO: add other actions here
|
||||
}
|
||||
b.actions[ac.ActionID] = action
|
||||
|
||||
// add this action's Run func to all events it is dependent on
|
||||
for _, input := range ac.Inputs {
|
||||
_, ok := b.events[*input.ActionID]
|
||||
if !ok {
|
||||
b.events[*input.ActionID] = make([]func(contentRow ContentRow) error, 0)
|
||||
}
|
||||
if input.ActionID != nil {
|
||||
b.events[*input.ActionID] = append(b.events[*input.ActionID], action.Run)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bus) ValidateAll() (bool, error) {
|
||||
problems := make(map[uuid.UUID]string)
|
||||
for key, a := range b.actions {
|
||||
err := a.Validate()
|
||||
if err != nil {
|
||||
problems[key] = err.Error()
|
||||
}
|
||||
|
||||
// TODO: check the input schemas against the actions they are coming from
|
||||
}
|
||||
if len(problems) != 0 {
|
||||
errorOut := ""
|
||||
for key, prob := range problems {
|
||||
if !strings.EqualFold("", errorOut) {
|
||||
errorOut = fmt.Sprintf("%v | %v: %v", errorOut, key, prob)
|
||||
} else {
|
||||
errorOut = fmt.Sprintf("%v: %v", key, prob)
|
||||
}
|
||||
}
|
||||
return false, errors.New(errorOut)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (b *Bus) Start() error {
|
||||
for _, a := range b.actions {
|
||||
aConfig := a.GetActionConfig()
|
||||
if len(aConfig.Inputs) <= 0 {
|
||||
err := a.Run(ContentRow{})
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("error running Action. ActionID: %v | %v", a.GetActionID(), err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bus) Run(actionID uuid.UUID, row ContentRow) error {
|
||||
for _, e := range b.events[actionID] {
|
||||
err := e(row)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("error running Action. ActionID: %v | %v", actionID, err.Error()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Bus will manage the transporting of the data from an actions output to all actions that are dependent upon that data.
|
||||
|
||||
If an action does not have a defined action it depends on it will be executed right away using the Run() func and will be
|
||||
passed a zeroed ContentRow.
|
||||
|
||||
Otherwise, all actions will have their run functions stored in arrays within a map of action uuids. When a certain action
|
||||
publishes a row then we loop through the array of stored functions that are waiting for data from that action.
|
||||
|
||||
|
||||
*/
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
||||
module github.com/payne8/metl
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/mitchellh/mapstructure v1.4.1
|
||||
)
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
95
metl.go
Normal file
95
metl.go
Normal file
@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
jsonFile, err := os.Open("metlTestConfig.json")
|
||||
// if we os.Open returns an error then handle it
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
fmt.Println("Successfully Opened metlTestConfig.json")
|
||||
// defer the closing of our jsonFile so that we can parse it later on
|
||||
defer jsonFile.Close()
|
||||
|
||||
// read our opened xmlFile as a byte array.
|
||||
byteValue, _ := ioutil.ReadAll(jsonFile)
|
||||
|
||||
// we initialize our Users array
|
||||
var metlConfig MetlConfig
|
||||
|
||||
// we unmarshal our byteArray which contains our
|
||||
// jsonFile's content into 'users' which we defined above
|
||||
err = json.Unmarshal(byteValue, &metlConfig)
|
||||
if err != nil {
|
||||
panic("error unmarshalling JSON into MetlConfig struct")
|
||||
}
|
||||
|
||||
bus, err := NewBus(metlConfig)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = bus.Start()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//stdInID, _ := uuid.Parse("a64b47b0-8ecb-409e-8fbc-5e2f5b93442b")
|
||||
//stdInID, _ := uuid.Parse("a64b47b0-8ecb-409e-8fbc-5e2f5b93442b")
|
||||
//
|
||||
//testConfig := MetlConfig{
|
||||
// ActionConfigs: []interface{}{
|
||||
// StdInConfig{
|
||||
// ActionConfig: ActionConfig{
|
||||
// ActionID: stdInID,
|
||||
// Type: StdIn,
|
||||
// OutputSchema: &Schema{
|
||||
// Columns: []ColumnDefinition{
|
||||
// {
|
||||
// Name: "id",
|
||||
// ID:
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// HasHeader: true,
|
||||
// },
|
||||
// ModifyColumnConfig{
|
||||
//
|
||||
// },
|
||||
// StdOutConfig{
|
||||
//
|
||||
// },
|
||||
// },
|
||||
//}
|
||||
|
||||
//fmt.Print("\033[H\033[2J")
|
||||
//for i := 0; i < 100; i++ {
|
||||
// fmt.Printf("\033[0;0H")
|
||||
// fmt.Printf("On %d/100\nfarts %d ", i, i)
|
||||
// //if isTerminal() {
|
||||
// //}
|
||||
// //else {
|
||||
// // fmt.Printf("On %d/100 ", i)
|
||||
// //}
|
||||
// time.Sleep(200 * time.Millisecond)
|
||||
//}
|
||||
}
|
||||
|
||||
func isTerminal() bool {
|
||||
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
276
metlTestConfig.json
Normal file
276
metlTestConfig.json
Normal file
@ -0,0 +1,276 @@
|
||||
{
|
||||
"actionConfigs": [
|
||||
{
|
||||
"ActionID": "a64b47b0-8ecb-409e-8fbc-5e2f5b93442b",
|
||||
"Type": "StdIn",
|
||||
"OutputSchema": {
|
||||
"Columns": [
|
||||
{
|
||||
"Name": "id",
|
||||
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||
"Type": "Integer",
|
||||
"DefaultValue": "0"
|
||||
},
|
||||
{
|
||||
"Name": "first_name",
|
||||
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "last_name",
|
||||
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "email",
|
||||
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "gender",
|
||||
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_make",
|
||||
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_model",
|
||||
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"Inputs": [],
|
||||
"hasHeader": true
|
||||
},
|
||||
{
|
||||
"ActionID": "30f7390c-c1cf-401b-aa81-d7156ddc5e1d",
|
||||
"Type": "ModifyColumn",
|
||||
"OutputSchema": {
|
||||
"Columns": [
|
||||
{
|
||||
"Name": "id",
|
||||
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||
"Type": "Integer",
|
||||
"DefaultValue": "0"
|
||||
},
|
||||
{
|
||||
"Name": "first_name",
|
||||
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "last_name",
|
||||
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "email",
|
||||
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "gender",
|
||||
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_make",
|
||||
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_model",
|
||||
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "new_column",
|
||||
"ID": "e9cff198-2ebd-425d-be59-9b5f834718df",
|
||||
"Type": "String",
|
||||
"DefaultValue": "x"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Inputs": [
|
||||
{
|
||||
"InputSchema": {
|
||||
"Columns": [
|
||||
{
|
||||
"Name": "id",
|
||||
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||
"Type": "Integer",
|
||||
"DefaultValue": "0"
|
||||
},
|
||||
{
|
||||
"Name": "first_name",
|
||||
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "last_name",
|
||||
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "email",
|
||||
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "gender",
|
||||
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_make",
|
||||
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_model",
|
||||
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"ActionID": "a64b47b0-8ecb-409e-8fbc-5e2f5b93442b"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ActionID": "c004eb92-24d7-453b-b474-bd49ac467f00",
|
||||
"Type": "StdOut",
|
||||
"OutputSchema": {
|
||||
"Columns": [
|
||||
{
|
||||
"Name": "id",
|
||||
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||
"Type": "Integer",
|
||||
"DefaultValue": "0"
|
||||
},
|
||||
{
|
||||
"Name": "first_name",
|
||||
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "last_name",
|
||||
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "email",
|
||||
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "gender",
|
||||
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_make",
|
||||
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_model",
|
||||
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "new_column",
|
||||
"ID": "e9cff198-2ebd-425d-be59-9b5f834718df",
|
||||
"Type": "String",
|
||||
"DefaultValue": "x"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Inputs": [
|
||||
{
|
||||
"InputSchema": {
|
||||
"Columns": [
|
||||
{
|
||||
"Name": "id",
|
||||
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||
"Type": "Integer",
|
||||
"DefaultValue": "0"
|
||||
},
|
||||
{
|
||||
"Name": "first_name",
|
||||
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "last_name",
|
||||
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "email",
|
||||
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "gender",
|
||||
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_make",
|
||||
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "car_model",
|
||||
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||
"Type": "String",
|
||||
"DefaultValue": ""
|
||||
},
|
||||
{
|
||||
"Name": "new_column",
|
||||
"ID": "e9cff198-2ebd-425d-be59-9b5f834718df",
|
||||
"Type": "String",
|
||||
"DefaultValue": "x"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ActionID": "30f7390c-c1cf-401b-aa81-d7156ddc5e1d"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user