Skip to content
This repository has been archived by the owner on Dec 5, 2019. It is now read-only.

Commit

Permalink
implement skeleton of a file generator. writing file is TBD.
Browse files Browse the repository at this point in the history
  • Loading branch information
huandu committed Aug 16, 2016
1 parent dfa7373 commit d236f31
Show file tree
Hide file tree
Showing 4 changed files with 381 additions and 0 deletions.
132 changes: 132 additions & 0 deletions tools/generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2015 Huan Du. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package main

import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)

type visitor func(node ast.Node) bool

func (v visitor) Visit(node ast.Node) ast.Visitor {
if v(node) {
return v
}

return nil
}

// Generator stores progress of package parser.
type Generator struct {
parsedPkgs map[string]bool
pkgs []string
context *Context
}

func (g *Generator) Parse() {
for i := 0; i < len(g.pkgs); i++ {
g.parsePkg(g.pkgs[i])
}
}

func (g *Generator) parsePkg(pkg string) {
pkgPath := filepath.Join(g.context.GoPackage, pkg)
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, pkgPath, func(info os.FileInfo) bool {
// Filter out all test files.
if strings.HasSuffix(info.Name(), "_test.go") {
return false
}

return true
}, parser.ParseComments)

if err != nil {
panic(err)
}

if _, ok := pkgs["main"]; ok {
delete(pkgs, "main")
}

if len(pkgs) != 1 {
keys := []string{}

for k, _ := range pkgs {
keys = append(keys, k)
}

panic(fmt.Errorf("there must be only one package name in a package. [pkgs:%v]", strings.Join(keys, ", ")))
}

//goDir := "go" + g.context.Version.Join("_")
//output := filepath.Join(g.context.Output, goDir, pkg)
//importPath := filepath.Join(g.context.ImportPath, goDir, pkg)

for _, p := range pkgs {
//name := p.Name
files := p.Files

for _, f := range files {
decls := f.Decls
neededDecls := []ast.Decl{}

for _, decl := range decls {
// Only type decl is needed.
if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
neededDecls = append(neededDecls, decl)
specs := genDecl.Specs

for _, spec := range specs {
ast.Walk(visitor(func(node ast.Node) bool {
if node == nil {
return false
}

switch n := node.(type) {
case *ast.TypeSpec:
typeName := n.Name.Name
logTracef("Find type. [type:%v]", typeName)

case *ast.SelectorExpr:
pkgName := n.X.(*ast.Ident).Name
typeName := n.Sel.Name

if pkgName == "C" {
break
}

logTracef("Find type. [package-name:%v] [type:%v]", pkgName, typeName)
}

return true
}), spec)
}
}
}

f.Decls = neededDecls
}
}
}

// Generate hacked files for packages.
// Basically, it extracts all types and generates hacked go files.
//
// Panic if it encounters any error.
func GenerateHackedFiles(context *Context, pkgs ...string) {
generator := &Generator{
parsedPkgs: make(map[string]bool),
pkgs: pkgs,
context: context,
}
generator.Parse()
}
23 changes: 23 additions & 0 deletions tools/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2015 Huan Du. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package main

import (
"fmt"
"os"
)

func logFatalf(format string, args ...interface{}) {
err := fmt.Errorf(format, args...)
panic(err)
}

func logErrorf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "ERROR: "+format+"\n", args...)
}

func logTracef(format string, args ...interface{}) {
fmt.Printf("TRACE: "+format+"\n", args...)
}
153 changes: 153 additions & 0 deletions tools/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright 2015 Huan Du. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package main

import (
"bytes"
"flag"
"os"
"path/filepath"
"regexp"
)

var (
flagGoSrc string
flagOutput string
flagImportPath string

reImportPath = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z0-9_]+)*(/[a-zA-Z0-9_]+)*$`)
)

type Context struct {
GoSrc string // Path to go src root directory.
GoPackage string // Path to package src in go src directory.
Version Version // Version number.
Output string // Path to output directory.
ImportPath string // The prefix of import path for output directory.
}

func init() {
flag.StringVar(&flagGoSrc, "go-src", "", "Path to go src directory.")
flag.StringVar(&flagOutput, "output", "", "Output path. Default is current directory.")
flag.StringVar(&flagImportPath, "import-path", "", "The prefix of import path for output directory.")
}

func validateGoSrc(context *Context) {
if flagGoSrc == "" {
logFatalf("Flag -go-src must be set.")
}

src, err := filepath.Abs(flagGoSrc)

if err != nil {
logFatalf("Fail to get absolute path of go src. [go-src:%v]", flagGoSrc)
}

info, err := os.Stat(src)

if err != nil {
logFatalf("Value of flag -go-src must be a valid directory name. [go-src:%v]", flagGoSrc)
}

if !info.Mode().IsDir() {
logFatalf("Value of flag -go-src must be a directory. [go-src:%v]", flagGoSrc)
}

srcsrc := filepath.Join(src, "src")
info, err = os.Stat(srcsrc)

if err != nil {
logFatalf("Fail to find `src` in go src directory. [go-src:%v]", flagGoSrc)
}

if !info.Mode().IsDir() {
logFatalf("The `src` in go src must be a directory. [go-src:%v]", flagGoSrc)
}

versionPath := filepath.Join(src, "VERSION")
versionFile, err := os.Open(versionPath)

if err != nil {
logFatalf("Fail to read VERSION in go src directory. Are you sure -go-src points to a valid go src directory? [go-src:%v]", flagGoSrc)
}

defer versionFile.Close()
versionBuf := &bytes.Buffer{}
versionBuf.ReadFrom(versionFile)

if versionBuf.Len() == 0 {
logFatalf("VERSION in go src is empty. [go-src:%v]", flagGoSrc)
}

version, err := ParseVersion(versionBuf.String())

if err != nil {
logFatalf("VERSION file content is not valid. [go-src:%v] [err:%v] [content:%v]", flagGoSrc, err, versionBuf.String())
}

context.GoSrc = src
context.GoPackage = srcsrc
context.Version = version
}

func validateOutput(context *Context) {
var err error

if flagOutput == "" {
flagOutput, err = os.Getwd()

if err != nil {
logFatalf("Value of flag -output is empty and current directory is not available.")
}
}

output, err := filepath.Abs(flagOutput)

if err != nil {
logFatalf("Fail to get absolute path of output. [output:%v]", flagOutput)
}

info, err := os.Stat(output)

if err != nil {
logFatalf("Value of flag -output must be a valid directory name. [output:%v]", flagOutput)
}

if !info.Mode().IsDir() {
logFatalf("Value of flag -output must be a directory. [output:%v]", flagOutput)
}

context.Output = output
}

func validateImportPath(context *Context) {
if flagImportPath == "" {
logFatalf("Flag -import-path must be set.")
}

if !reImportPath.MatchString(flagImportPath) {
logFatalf("Value of flag -import-path must be a valid import path. [import-path:%v]", flagImportPath)
}

context.ImportPath = flagImportPath
}

func main() {
defer func() {
if err := recover(); err != nil {
logErrorf("%v", err)
os.Exit(1)
}
}()

flag.Parse()

context := &Context{}
validateGoSrc(context)
validateOutput(context)
validateImportPath(context)

GenerateHackedFiles(context, "runtime")
}
73 changes: 73 additions & 0 deletions tools/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2015 Huan Du. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package main

import (
"fmt"
"regexp"
"strings"
)

var reVersion = regexp.MustCompile(`^go(\d+)(\.(\d+))+`)

type Version []string

// Parse version string like "go1.6.3".
func ParseVersion(version string) (Version, error) {
matches := reVersion.FindStringSubmatch(version)

if matches == nil {
return nil, fmt.Errorf("invalid version format")
}

v := Version{}

for i := 1; i < len(matches); i += 2 {
v = append(v, matches[i])
}

return v, nil
}

// Return a version string like "go1.6.3".
func (v Version) String() string {
return "go" + v.Join(".")
}

// Join version numbers with sep.
func (v Version) Join(sep string) string {
return strings.Join(([]string)(v), sep)
}

// Compare two versions.
// Return 1 if a > b.
// Return 0 if two versions equal.
// Return -1 if a < b.
func CompareVersions(a, b Version) int {
if a == nil && b != nil {
return -1
}

if a != nil && b == nil {
return 1
}

la := len(a)
lb := len(b)

for i := 0; i < la && i < lb; i++ {
if c := strings.Compare(a[i], b[i]); c != 0 {
return c
}
}

if la > lb {
return 1
} else if la < lb {
return -1
} else {
return 0
}
}

0 comments on commit d236f31

Please sign in to comment.