Replies: 5 comments 20 replies
-
demo: package magic
import (
"fmt"
"regexp"
"strings"
"testing"
)
/*magics:
line:
- !: exec cmd
- %args: cmd arg for main
- %autoget and %noautoget: Default is %autoget, auto get pkg
- %cd [<directory>]: Change current directory of the Go kernel, default it notebook dir
- %env VAR value: available both for Go code and for shell scripts.
- %goflags: go build, "" will reset
- %with_inputs
- %with_password
- %widgets
- %widgets_hb
- %goworkfix:
- %track: [file_or_directory], list tracked if no arg
- %untrack [file_or_directory][...] ... => *, list tracked if no arg
- %list/ls
- %remove/rm
- %reset [go.mod]: If the optional go.mod parameter is given, it will re-initialize only the go.mod file
cell:
- %%: wrap main func, allow cmd arg, overwrite %args
- %main wrap main func, allow cmd arg, overwrite %args
- %test: current whole cell!!!!!!!!!!!!!!!!!!!!!
- %%bash: Run cells with bash in a subprocess. same with %%script bash
- %%script:
*/
type Magic interface {
// todo args: kernel.Message, *goexec.State
Exec() error
}
// todo args: kernel.Message, *goexec.State
type (
LineExecutor func(*LineMagicContext) error
CellExecutor func(*CellMagicContext) error
)
type LineMagicContext struct {
Name string
RawArgs string
Descriptors string // `*` for project dir
executor LineExecutor
}
type CellMagicContext struct {
Name string
RawArgs string
Body []string
executor CellExecutor
}
func (m *LineMagicContext) Exec() error {
return m.executor(m)
}
func (m *CellMagicContext) Exec() error {
return m.executor(m)
}
// magic container
var (
LineMagics map[string]LineExecutor
CellMagics map[string]CellExecutor
)
// magic command regexp do not match leading spaces to prevent misjudgment
var (
lineMagicRegexp = regexp.MustCompile(`^(?P<descriptors>!\*?|%)(?P<name>\w+)\s*(?P<args>.*\S)?\s*`)
cellMagicRegexp = regexp.MustCompile(`^%%(?P<name>\w*)\s*(?P<args>.*\S)?\s*`)
)
// resolveLineMagic from line, returns lineContinue, magicContext, err
func resolveLineMagic(line string) (bool, *LineMagicContext, error) {
if matches := lineMagicRegexp.FindStringSubmatch(line); matches != nil {
name := matches[lineMagicRegexp.SubexpIndex("name")]
if executor, ok := LineMagics[name]; ok {
args := matches[lineMagicRegexp.SubexpIndex("args")]
lineContinue := args != "" && args[len(args)-1] == '\\' && !strings.HasSuffix(args, `\\`)
magic := &LineMagicContext{
Name: name,
RawArgs: args,
Descriptors: matches[lineMagicRegexp.SubexpIndex("descriptors")],
executor: executor,
}
return lineContinue, magic, nil
} else {
return false, nil, fmt.Errorf("line magic %v not found", name)
}
}
return false, nil, nil
}
// resolveCellMagic from line, returns (magicContext, err)
func resolveCellMagic(line string) (*CellMagicContext, error) {
if matches := cellMagicRegexp.FindStringSubmatch(line); matches != nil {
name := matches[cellMagicRegexp.SubexpIndex("name")]
if executor, ok := CellMagics[name]; ok {
magic := &CellMagicContext{
Name: name,
RawArgs: matches[cellMagicRegexp.SubexpIndex("args")],
Body: make([]string, 0, 8),
executor: executor,
}
return magic, nil
} else {
return nil, fmt.Errorf("cell magic %v not found", name)
}
}
return nil, nil
}
func Resolve(src string) []Magic {
magics := make([]Magic, 0, 4)
var previous Magic
lineMagicContinue := false
for _, line := range strings.Split(src, "\n") {
// join line if line magic is end with `\`(but not end with `\\`)
if lineMagic, ok := previous.(*LineMagicContext); ok && lineMagicContinue {
lineMagic.RawArgs += " " + line
line = strings.TrimRight(line, " ")
lineMagicContinue = line != "" && line[len(line)-1] == '\\' && !strings.HasSuffix(line, `\\`)
continue
}
var magic Magic
// resolve line magic
lineContinue, lineMagic, err := resolveLineMagic(line)
if err != nil {
panic(err)
}
lineMagicContinue = lineContinue
if lineMagic != nil {
magic = lineMagic
} else {
// resolve cell magic
cellMagic, err := resolveCellMagic(line)
if err != nil {
panic(err)
}
if cellMagic != nil {
magic = cellMagic
}
}
if magic != nil {
magics = append(magics, magic)
previous = magic
} else {
// not magic, append to previous cell magic
blankLine := strings.TrimLeft(line, " ") == ""
// create main.go if previous is nil
if previous == nil && !blankLine {
previous = &CellMagicContext{
Name: "/main.go",
RawArgs: "",
Body: make([]string, 0, 8),
executor: _goexec,
}
magics = append(magics, previous)
}
if cellMagic, ok := previous.(*CellMagicContext); ok &&
// skip leading blank lines, but force append if no line
(!blankLine || len(cellMagic.Body) == 0 || strings.TrimLeft(cellMagic.Body[len(cellMagic.Body)-1], " ") != "") {
cellMagic.Body = append(cellMagic.Body, line)
}
}
}
return magics
}
/* ------------ tmp debug start ------------ */
func _goexec(m *CellMagicContext) error {
fmt.Println("goExec", m.RawArgs, len(m.Body), m.Body)
return nil
}
func _goMainWrapper(m *CellMagicContext) error {
fmt.Println("goMainWrapper", m.RawArgs, len(m.Body), m.Body)
return nil
}
/* ------------ tmp debug end ------------ */
/* ------------ register start ------------ */
func init() {
RegisterLineMagic([]string{"ls", "list"}, func(m *LineMagicContext) error {
fmt.Printf("run line magic `%v%v` `%v`\n", m.Descriptors, m.Name, m.RawArgs)
return nil
})
RegisterLineMagic([]string{"pwd"}, func(m *LineMagicContext) error {
fmt.Printf("run line magic `%v%v` `%v`\n", m.Descriptors, m.Name, m.RawArgs)
return nil
})
RegisterCellMagic([]string{"", "main"}, _goMainWrapper)
}
var (
MagicIllegal = fmt.Errorf("magic failed to register")
)
func RegisterLineMagic(names []string, executor LineExecutor) error {
if len(names) < 1 || executor == nil {
return fmt.Errorf("line %w: names and executor cannot be zero value", MagicIllegal)
}
// validate
hasName := false
exist := false
for _, name := range names {
if !hasName && name != "" {
hasName = true
}
if _, exist = LineMagics[name]; exist {
break
}
}
if exist {
return fmt.Errorf("line %w: already exists", MagicIllegal)
}
if !hasName {
return fmt.Errorf("line %w: name cannot be empty", MagicIllegal)
}
// init map
if LineMagics == nil {
LineMagics = make(map[string]LineExecutor, 12)
}
// register
for _, name := range names {
if name != "" {
LineMagics[name] = executor
}
}
return nil
}
func RegisterCellMagic(names []string, executor CellExecutor) error {
if len(names) < 1 || executor == nil {
return fmt.Errorf("cell %w: names and executor cannot be zero value", MagicIllegal)
}
// validate
exist := false
for _, name := range names {
if _, exist = CellMagics[name]; exist {
break
}
}
if exist {
return fmt.Errorf("line %w: already exists", MagicIllegal)
}
// init map
if CellMagics == nil {
CellMagics = make(map[string]CellExecutor, 12)
}
// register
for _, name := range names {
CellMagics[name] = executor
}
return nil
}
/* ------------ register end ------------ */
func TestTransform(t *testing.T) {
src := `
os.Create("zz-1.log")
%%
os.Create("zz-2.log")
!ls -ahl *.log # sas\
!pwd -k \\
!*pwd
`
magics := Resolve(src)
// todo merge goexec/goMainWrapper
for _, magic := range magics {
magic.Exec()
}
} |
Beta Was this translation helpful? Give feedback.
-
So ... to simplify, what about:
And ? The registration code is simple to do, the question is more whether if it is worth it ? -- so far we only have 2 cell magics, and the line magic won't gain that much. |
Beta Was this translation helpful? Give feedback.
-
@janpfeifer example:
cell-2:
cell-3:
|
Beta Was this translation helpful? Give feedback.
-
createGoFileFromLines (https://github.com/janpfeifer/gonb/blob/main/internal/goexec/composer.go#L308 ) should remove |
Beta Was this translation helpful? Give feedback.
-
I has some questions/ideas about kernel, I can contribute to this if we agree on something.
lslmagic/lslinemagic
lscmagic/lscellmagic
Beta Was this translation helpful? Give feedback.
All reactions