Skip to content

Commit

Permalink
Implement go fmt functionality (fix #16)
Browse files Browse the repository at this point in the history
  • Loading branch information
yunabe committed May 3, 2018
1 parent 47380fc commit 5c2f331
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 5 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ If you are using Linux or Mac OS, you can use start/stop scripts instead. Web br
- Run `jupyter notebook` command to start Juyputer Notebook and select "Go (lgo)" from New Notebook menu.
- To show documents of packages, functions and variables in your code, move the cursor to the identifier you want to inspect and press `Shift-Tab`.
- Press `Tab` to complete code
- Click `Format Go` button in the toolbar to format code.
- lgo works with [JupyterLab](https://github.com/jupyterlab/jupyterlab). To use lgo from JupyterLab, install JupyterLab and run `jupyter lab`.

<img width="400" height="225" src="doc/inspect.jpg">
Expand Down Expand Up @@ -195,25 +196,25 @@ gore is a CLI tool and it does not support Jupyter Notebook.
|Backend|gc (go compiler)|An unofficial interpreter|
|Full Go Language Specs|:heavy_check_mark:||
|100% gc compatible|:heavy_check_mark:||
|Type Safety|:heavy_check_mark:||
|Static typing|:heavy_check_mark:|to some extent|
|Performance|Fast|Slow|
|Overhead|500ms|1ms|
|[Cancellation](https://github.com/yunabe/lgo/blob/master/README.md#cancellation)|:heavy_check_mark:||
|Code completion|:heavy_check_mark:||
|Code inspection|:heavy_check_mark:||
|Code formatting|:heavy_check_mark:||
|[Display HTML and images](https://github.com/yunabe/lgo/blob/master/README.md#display-html-and-images)|:heavy_check_mark:||
|Windows, Mac|Use Docker or VM|Partial|
|License|BSD|LGPL|

[gophernotes](https://github.com/gopherdata/gophernotes) is the first Jupyter kernel for Go, released in Jan 2016.
gophernotes was the first Jupyter kernel for Go, released in Jan 2016.
Before [Sep 2017](https://github.com/gopherdata/gophernotes/commit/69792d8af799d6905e2c576164d1a189ac021784#diff-04c6e90faac2675aa89e2176d2eec7d8), it used the same technology gore uses to evaluate Go code. This means it did not fit to heavy data processing or data analysis at all.
From Sep 2017, gophernotes switched from `go run` approach to [gomacro](https://github.com/cosmos72/gomacro), one of unofficial golang interpreters by [cosmos72](https://github.com/cosmos72). This solved the problem gore has. Now, gophernotes is a great tool for data science in Go.
From Sep 2017, gophernotes switched from `go run` approach to [gomacro](https://github.com/cosmos72/gomacro), one of unofficial golang interpreters by [cosmos72](https://github.com/cosmos72). This solved the problem gore has. Now, the code execution mechnism of gophernotes also fits to heavy data analysis.

The shortcomings of using an unofficial interpreter are
- It does not support all Go language features. Especially, it does not support one of the most important Go feature, `interface`.
As of go1.10, it is hard to support `interface` in an interpreter written in Go because of the lack of API in `reflect` package.
- Interpreters are generally slow.
- Type unsafe. At least, gomacro is not statically typed.
- Unofficial interpreters are not well-tested compared to the official gc (go compiler) tools.

The advantages of this approach are
Expand Down
8 changes: 7 additions & 1 deletion bin/install_kernel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ from __future__ import print_function
import argparse
import json
import os
import shutil
import sys

from jupyter_client.kernelspec import KernelSpecManager
Expand All @@ -29,7 +30,12 @@ def install_kernel_spec(binary, user, prefix):
os.chmod(td, 0o755) # Starts off as 700, not user readable
with open(os.path.join(td, 'kernel.json'), 'w') as f:
json.dump(kernel_json, f, sort_keys=True)
# TODO: Copy any resources

resources = os.path.join(os.path.dirname(__file__), 'kernel_resources')
for item in os.listdir(resources):
src = os.path.join(resources, item)
if not os.path.isdir(src):
shutil.copy(src, os.path.join(td, item))

print('Installing Jupyter kernel spec')
KernelSpecManager().install_kernel_spec(td, 'lgo', user=user, replace=True, prefix=prefix)
Expand Down
37 changes: 37 additions & 0 deletions bin/kernel_resources/kernel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Kernel specific extension for lgo.
// http://jupyter-notebook.readthedocs.io/en/stable/extending/frontend_extensions.html#kernel-specific-extensions

define(function(){
var formatCells = function () {
var cells = Jupyter.notebook.get_selected_cells();
for (var i = 0; i < cells.length; i++) {
(function(){
var editor = cells[i].code_mirror;
var msg = {code: editor.getValue()};
var cb = function(msg) {
if (!msg || !msg.content || msg.content.status != 'ok') {
// TODO: Show an error message.
return;
}
editor.setValue(msg.content.code);
};
Jupyter.notebook.kernel.send_shell_message("gofmt_request", msg, {shell: {reply: cb}});
})();
}
};

var action = {
icon: 'fa-align-left', // a font-awesome class used on buttons, etc
help : 'Format Go',
handler : formatCells
};
var prefix = 'lgo-kernel';
var actionName = 'format-code';

var fullActionName = Jupyter.actions.register(action, actionName, prefix);
Jupyter.toolbar.add_buttons_group([fullActionName]);

return {
onload: function(){}
}
});
12 changes: 12 additions & 0 deletions cmd/lgo-internal/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/yunabe/lgo/cmd/lgo-internal/liner"
"github.com/yunabe/lgo/cmd/runner"
"github.com/yunabe/lgo/core"
"github.com/yunabe/lgo/converter"
scaffold "github.com/yunabe/lgo/jupyter/gojupyterscaffold"
)

Expand Down Expand Up @@ -242,6 +243,17 @@ func (*handlers) HandleIsComplete(req *scaffold.IsCompleteRequest) *scaffold.IsC
}
}

func (*handlers) HandleGoFmt(req *scaffold.GoFmtRequest) (*scaffold.GoFmtReply, error) {
formatted, err := converter.Format(req.Code)
if err != nil {
return nil, err
}
return &scaffold.GoFmtReply{
Status: "ok",
Code: formatted,
}, nil
}

// kernelLogWriter forwards messages to the current os.Stderr, which is change on every execution.
type kernelLogWriter struct{}

Expand Down
5 changes: 5 additions & 0 deletions jupyter/gojupyterscaffold-example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"html"
Expand Down Expand Up @@ -99,6 +100,10 @@ func (*handlers) HandleIsComplete(req *scaffold.IsCompleteRequest) *scaffold.IsC
return nil
}

func (*handlers) HandleGoFmt(req *scaffold.GoFmtRequest) (*scaffold.GoFmtReply, error) {
return nil, errors.New("not implemented")
}

func main() {
flag.Parse()
fmt.Printf("os.Args == %+v\n", os.Args)
Expand Down
17 changes: 17 additions & 0 deletions jupyter/gojupyterscaffold/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type RequestHandlers interface {
HandleInspect(req *InspectRequest) *InspectReply
// http://jupyter-client.readthedocs.io/en/latest/messaging.html#code-completeness
HandleIsComplete(req *IsCompleteRequest) *IsCompleteReply
HandleGoFmt(req *GoFmtRequest) (*GoFmtReply, error)
}

type KernelInfo struct {
Expand All @@ -40,6 +41,13 @@ type ExecuteRequest struct {
StopOnError bool `json:"stop_on_error"`
}

// See http://jupyter-client.readthedocs.io/en/stable/messaging.html#request-reply
type errorReply struct {
Status string `json:"status"` // status must be always "error"
Ename string `json:"ename,omitempty"`
Evalue string `json:"evalue,omitempty"`
}

// See http://jupyter-client.readthedocs.io/en/latest/messaging.html#introspection
type InspectRequest struct {
Code string `json:"code"`
Expand Down Expand Up @@ -130,3 +138,12 @@ type IsCompleteReply struct {
// field does not exist.
Indent string `json:"indent"`
}

type GoFmtRequest struct {
Code string `json:"code"`
}

type GoFmtReply struct {
Status string `json:"status"`
Code string `json:"code"`
}
2 changes: 2 additions & 0 deletions jupyter/gojupyterscaffold/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ func newContentForMsgType(header *messageHeader) interface{} {
return &InspectRequest{}
case "is_complete_request":
return &IsCompleteRequest{}
case "gofmt_request":
return &GoFmtRequest{}
}
return nil
}
Expand Down
17 changes: 17 additions & 0 deletions jupyter/gojupyterscaffold/shelsocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ func (s *shellSocket) handleMessages() error {
if err != nil {
return fmt.Errorf("Failed to unmarshal messages from %s: %v", s.name, err)
}
glog.Warningf("MsgType in %s: %q", s.name, msg.Header.MsgType)
switch typ := msg.Header.MsgType; typ {
case "kernel_info_request":
if err := s.sendKernelInfo(&msg); err != nil {
Expand Down Expand Up @@ -373,6 +374,22 @@ func (s *shellSocket) handleMessages() error {
res.Content = reply
s.pushResult(res)
}()
case "gofmt_request":
go func() {
res := newMessageWithParent(&msg)
res.Header.MsgType = "gofmt_reply"
reply, err := s.handlers.HandleGoFmt(msg.Content.(*GoFmtRequest))
if err != nil {
res.Content = &errorReply{
Status: "error",
Ename: "error",
Evalue: err.Error(),
}
} else {
res.Content = reply
}
s.pushResult(res)
}()
default:
glog.Warningf("Unsupported MsgType in %s: %q", s.name, typ)
}
Expand Down

0 comments on commit 5c2f331

Please sign in to comment.