...
This commit is contained in:
parent
3d66a03e2e
commit
61d36470ce
3
examples/simple/golang/go.mod
Normal file
3
examples/simple/golang/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module efprojects.com/kitten-ipc/example/simple
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
43
examples/simple/golang/main.go
Normal file
43
examples/simple/golang/main.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
kittenipc "efprojects.com/kitten-ipc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// kittenipc:api
|
||||||
|
type IpcApi struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api IpcApi) Div(a int, b int) (int, error) {
|
||||||
|
if b == 0 {
|
||||||
|
return 0, fmt.Errorf("zero division")
|
||||||
|
}
|
||||||
|
return a / b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
api := IpcApi{}
|
||||||
|
|
||||||
|
cmdStr := fmt.Sprintf("node %s", path.Join(cwd, "..", "ts/index.js"))
|
||||||
|
cmd := exec.Command(cmdStr)
|
||||||
|
kit := kittenipc.New(cmd, &api)
|
||||||
|
|
||||||
|
if err := kit.Start(); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kit.Wait(); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
0
examples/simple/ts/index.ts
Normal file
0
examples/simple/ts/index.ts
Normal file
12
examples/simple/ts/package.json
Normal file
12
examples/simple/ts/package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "kitten-example-simple",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
44
examples/simple/ts/tsconfig.json
Normal file
44
examples/simple/ts/tsconfig.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
// Visit https://aka.ms/tsconfig to read more about this file
|
||||||
|
"compilerOptions": {
|
||||||
|
// File Layout
|
||||||
|
// "rootDir": "./src",
|
||||||
|
// "outDir": "./dist",
|
||||||
|
|
||||||
|
// Environment Settings
|
||||||
|
// See also https://aka.ms/tsconfig/module
|
||||||
|
"module": "nodenext",
|
||||||
|
"target": "esnext",
|
||||||
|
"types": [],
|
||||||
|
// For nodejs:
|
||||||
|
// "lib": ["esnext"],
|
||||||
|
// "types": ["node"],
|
||||||
|
// and npm install -D @types/node
|
||||||
|
|
||||||
|
// Other Outputs
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
|
||||||
|
// Stricter Typechecking Options
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
|
||||||
|
// Style Options
|
||||||
|
// "noImplicitReturns": true,
|
||||||
|
// "noImplicitOverride": true,
|
||||||
|
// "noUnusedLocals": true,
|
||||||
|
// "noUnusedParameters": true,
|
||||||
|
// "noFallthroughCasesInSwitch": true,
|
||||||
|
// "noPropertyAccessFromIndexSignature": true,
|
||||||
|
|
||||||
|
// Recommended Options
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noUncheckedSideEffectImports": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
7
go.work
Normal file
7
go.work
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
use (
|
||||||
|
golang
|
||||||
|
kitcom
|
||||||
|
examples/simple/golang
|
||||||
|
)
|
||||||
14
go.work.sum
Normal file
14
go.work.sum
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
|
||||||
|
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
|
||||||
3
golang/go.mod
Normal file
3
golang/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module efprojects.com/kitten-ipc
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
12
golang/lib.go
Normal file
12
golang/lib.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package kittenipc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KittenIPC struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cmd *exec.Cmd, api any) *KittenIPC {
|
||||||
|
|
||||||
|
}
|
||||||
5
kitcom/go.mod
Normal file
5
kitcom/go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module efprojects.com/kitten-ipc/kitcom
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
require golang.org/x/tools v0.38.0 // indirect
|
||||||
2
kitcom/go.sum
Normal file
2
kitcom/go.sum
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
1
kitcom/gogen.go
Normal file
1
kitcom/gogen.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package main
|
||||||
106
kitcom/goparser.go
Normal file
106
kitcom/goparser.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var decorComment = regexp.MustCompile(`^//\s?kittenipc:api$`)
|
||||||
|
|
||||||
|
type apiStruct struct {
|
||||||
|
pkgName string
|
||||||
|
name string
|
||||||
|
methods []*ast.FuncDecl
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoApiParser struct {
|
||||||
|
apiStructs []*apiStruct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GoApiParser) Parse(sourceFile string) (Api, error) {
|
||||||
|
|
||||||
|
fileSet := token.NewFileSet()
|
||||||
|
astFile, err := parser.ParseFile(fileSet, sourceFile, nil, parser.ParseComments|parser.SkipObjectResolution)
|
||||||
|
if err != nil {
|
||||||
|
return Api{}, fmt.Errorf("parse file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgName := astFile.Name.Name
|
||||||
|
|
||||||
|
for _, decl := range astFile.Decls {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Doc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// use only last comment. https://tip.golang.org/doc/comment#syntax
|
||||||
|
lastComment := genDecl.Doc.List[len(genDecl.Doc.List)-1]
|
||||||
|
if !decorComment.MatchString(lastComment.Text) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
typeSpec, ok := genDecl.Specs[0].(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
structType, ok := typeSpec.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_ = structType
|
||||||
|
|
||||||
|
g.apiStructs = append(g.apiStructs, &apiStruct{
|
||||||
|
name: typeSpec.Name.Name,
|
||||||
|
pkgName: pkgName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.apiStructs) == 0 {
|
||||||
|
// todo support arbitrary order of input files
|
||||||
|
return Api{}, fmt.Errorf("no api struct found")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, decl := range astFile.Decls {
|
||||||
|
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !funcDecl.Name.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if funcDecl.Recv == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reciever := funcDecl.Recv.List[0]
|
||||||
|
recvType := reciever.Type
|
||||||
|
|
||||||
|
star, isPointer := recvType.(*ast.StarExpr)
|
||||||
|
if isPointer {
|
||||||
|
recvType = star.X
|
||||||
|
}
|
||||||
|
|
||||||
|
recvIdent, ok := recvType.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, apiStrct := range g.apiStructs {
|
||||||
|
if recvIdent.Name == apiStrct.name && pkgName == apiStrct.pkgName {
|
||||||
|
apiStrct.methods = append(apiStrct.methods, funcDecl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Api{}, nil
|
||||||
|
}
|
||||||
241
kitcom/habr.go
Normal file
241
kitcom/habr.go
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
//go:build exclude
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/printer"
|
||||||
|
"go/token"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ast/inspector"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Шаблон, на основе которого будем генерировать
|
||||||
|
// .EntityName, .PrimaryType — параметры,
|
||||||
|
// в которые будут установлены данные, добытые из AST-модели
|
||||||
|
var repositoryTemplate = template.Must(template.New("").Parse(`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type {{ .EntityName }}Repository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New{{ .EntityName }}Repository(db *gorm.DB) {{ .EntityName }}Repository {
|
||||||
|
return {{ .EntityName }}Repository{ db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r {{ .EntityName }}Repository) Get({{ .PrimaryName }} {{ .PrimaryType}}) (*{{ .EntityName }}, error) {
|
||||||
|
entity := new({{ .EntityName }})
|
||||||
|
err := r.db.Limit(1).Where("{{ .PrimarySQLName }} = ?", {{ .PrimaryName }}).Find(entity).Error()
|
||||||
|
return entity, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (r {{ .EntityName }}Repository) Create(entity *{{ .EntityName }}) error {
|
||||||
|
return r.db.Create(entity).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r {{ .EntityName }}Repository) Update(entity *{{ .EntityName }}) error {
|
||||||
|
return r.db.Model(entity).Update.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r {{ .EntityName }}Repository) Update(entity *{{ .EntityName }}) error {
|
||||||
|
return r.db.Model(entity).Update.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r {{ .EntityName }}Repository) Delete(entity *{{ .EntityName }}) error {
|
||||||
|
return r.db.Delete.Error
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
// Агрегатор данных для установки параметров в шаблоне
|
||||||
|
type repositoryGenerator struct {
|
||||||
|
typeSpec *ast.TypeSpec
|
||||||
|
structType *ast.StructType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Просто helper-функция для печати замысловатого ast.Expr в обычный string
|
||||||
|
func expr2string(expr ast.Expr) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := printer.Fprint(&buf, token.NewFileSet(), expr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error print expression to string: #{err}")
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper для извлечения поля структуры,
|
||||||
|
// которое станет первичным ключом в таблице DB
|
||||||
|
// Поиск поля ведётся по тегам
|
||||||
|
// Ищем то, что мы пометили gorm:"primary_key"
|
||||||
|
func (r repositoryGenerator) primaryField() (*ast.Field, error) {
|
||||||
|
for _, field := range r.structType.Fields.List {
|
||||||
|
if !strings.Contains(field.Tag.Value, "primary") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return field, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("has no primary field")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Собственно, генератор
|
||||||
|
// оформлен методом структуры repositoryGenerator,
|
||||||
|
// так что параметры передавать не нужно:
|
||||||
|
// они уже аккумулированы в ресивере метода r repositoryGenerator
|
||||||
|
// Передаём ссылку на ast.File,
|
||||||
|
// в котором и окажутся плоды трудов
|
||||||
|
func (r repositoryGenerator) Generate(outFile *ast.File) error {
|
||||||
|
//Находим первичный ключ
|
||||||
|
primary, err := r.primaryField()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//Аллокация и установка параметров для template
|
||||||
|
params := struct {
|
||||||
|
EntityName string
|
||||||
|
PrimaryName string
|
||||||
|
PrimarySQLName string
|
||||||
|
PrimaryType string
|
||||||
|
}{
|
||||||
|
//Параметры извлекаем из ресивера метода
|
||||||
|
EntityName: r.typeSpec.Name.Name,
|
||||||
|
PrimaryName: primary.Names[0].Name,
|
||||||
|
PrimarySQLName: primary.Names[0].Name,
|
||||||
|
PrimaryType: expr2string(primary.Type),
|
||||||
|
}
|
||||||
|
//Аллокация буфера,
|
||||||
|
//куда будем заливать выполненный шаблон
|
||||||
|
var buf bytes.Buffer
|
||||||
|
//Процессинг шаблона с подготовленными параметрами
|
||||||
|
//в подготовленный буфер
|
||||||
|
err = repositoryTemplate.Execute(&buf, params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("execute template: %v", err)
|
||||||
|
}
|
||||||
|
//Теперь сделаем парсинг обработанного шаблона,
|
||||||
|
//который уже стал валидным кодом Go,
|
||||||
|
//в дерево разбора,
|
||||||
|
//получаем AST этого кода
|
||||||
|
templateAst, err := parser.ParseFile(
|
||||||
|
token.NewFileSet(),
|
||||||
|
//Источник для парсинга лежит не в файле,
|
||||||
|
"",
|
||||||
|
//а в буфере
|
||||||
|
buf.Bytes(),
|
||||||
|
//mode парсинга, нас интересуют в основном комментарии
|
||||||
|
parser.ParseComments,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse template: %v", err)
|
||||||
|
}
|
||||||
|
//Добавляем декларации из полученного дерева
|
||||||
|
//в результирующий outFile *ast.File,
|
||||||
|
//переданный нам аргументом
|
||||||
|
for _, decl := range templateAst.Decls {
|
||||||
|
outFile.Decls = append(outFile.Decls, decl)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
//Цель генерации передаётся переменной окружения
|
||||||
|
path := os.Getenv("GOFILE")
|
||||||
|
if path == "" {
|
||||||
|
log.Fatal("GOFILE must be set")
|
||||||
|
}
|
||||||
|
//Разбираем целевой файл в AST
|
||||||
|
astInFile, err := parser.ParseFile(
|
||||||
|
token.NewFileSet(),
|
||||||
|
path,
|
||||||
|
nil,
|
||||||
|
//Нас интересуют комментарии
|
||||||
|
parser.ParseComments,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parse file: %v", err)
|
||||||
|
}
|
||||||
|
//Для выбора интересных нам деклараций
|
||||||
|
//используем Inspector из golang.org/x/tools/go/ast/inspector
|
||||||
|
i := inspector.New([]*ast.File{astInFile})
|
||||||
|
//Подготовим фильтр для этого инспектора
|
||||||
|
iFilter := []ast.Node{
|
||||||
|
//Нас интересуют декларации
|
||||||
|
&ast.GenDecl{},
|
||||||
|
}
|
||||||
|
//Выделяем список заданий генерации
|
||||||
|
var genTasks []repositoryGenerator
|
||||||
|
//Запускаем инспектор с подготовленным фильтром
|
||||||
|
//и литералом фильтрующей функции
|
||||||
|
i.Nodes(iFilter, func(node ast.Node, push bool) (proceed bool) {
|
||||||
|
genDecl := node.(*ast.GenDecl)
|
||||||
|
//Код без комментариев не нужен,
|
||||||
|
if genDecl.Doc == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
//интересуют спецификации типов,
|
||||||
|
typeSpec, ok := genDecl.Specs[0].(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
//а конкретно структуры
|
||||||
|
structType, ok := typeSpec.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
//Из оставшегося
|
||||||
|
for _, comment := range genDecl.Doc.List {
|
||||||
|
switch comment.Text {
|
||||||
|
//выделяем структуры, помеченные комментарием repogen:entity,
|
||||||
|
case "//repogen:entity":
|
||||||
|
//и добавляем в список заданий генерации
|
||||||
|
genTasks = append(genTasks, repositoryGenerator{
|
||||||
|
typeSpec: typeSpec,
|
||||||
|
structType: structType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
//Аллокация результирующего дерева разбора
|
||||||
|
astOutFile := &ast.File{
|
||||||
|
Name: astInFile.Name,
|
||||||
|
}
|
||||||
|
//Запускаем список заданий генерации
|
||||||
|
for _, task := range genTasks {
|
||||||
|
//Для каждого задания вызываем написанный нами генератор
|
||||||
|
//как метод этого задания
|
||||||
|
//Сгенерированные декларации помещаются в результирующее дерево разбора
|
||||||
|
err = task.Generate(astOutFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("generate: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Подготовим файл конечного результата всей работы,
|
||||||
|
//назовем его созвучно файлу модели, добавим только суффикс _gen
|
||||||
|
outFile, err := os.Create(strings.TrimSuffix(path, ".go") + "_gen.go")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("create file: %v", err)
|
||||||
|
}
|
||||||
|
//Не забываем прибраться
|
||||||
|
defer outFile.Close()
|
||||||
|
//Печатаем результирующий AST в результирующий файл исходного кода
|
||||||
|
//«Печатаем» не следует понимать буквально,
|
||||||
|
//дерево разбора нельзя просто переписать в файл исходного кода,
|
||||||
|
//это совершенно разные форматы
|
||||||
|
//Мы здесь воспользуемся специализированным принтером из пакета ast/printer
|
||||||
|
err = printer.Fprint(outFile, token.NewFileSet(), astOutFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("print file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
87
kitcom/main.go
Normal file
87
kitcom/main.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProgLang string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Golang ProgLang = "Golang"
|
||||||
|
TypeScript = "Typescript"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Src string
|
||||||
|
Dest string
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseFlags() {
|
||||||
|
flag.StringVar(&Src, "src", "", "Source file")
|
||||||
|
flag.StringVar(&Dest, "dest", "", "Dest file")
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Api struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiParser interface {
|
||||||
|
Parse(sourceFile string) (Api, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// todo support go:generate
|
||||||
|
//goFile := os.Getenv("GOFILE")
|
||||||
|
//if goFile == "" {
|
||||||
|
// log.Panic("GOFILE must be set")
|
||||||
|
//}
|
||||||
|
|
||||||
|
parseFlags()
|
||||||
|
if Src == "" || Dest == "" {
|
||||||
|
log.Panic("source and destination must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkIsFile(Src); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiParser, err := apiParserByExt(Src)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = apiParser.Parse(Src)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkIsFile(src string) error {
|
||||||
|
info, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat file: %w", err)
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return fmt.Errorf("%s is a directory; directories are not supported yet", src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiParserByExt(src string) (ApiParser, error) {
|
||||||
|
switch path.Ext(src) {
|
||||||
|
case ".go":
|
||||||
|
return &GoApiParser{}, nil
|
||||||
|
case ".ts":
|
||||||
|
return &TypescriptApiParser{}, nil
|
||||||
|
case ".js":
|
||||||
|
return nil, fmt.Errorf("vanilla javascript is not supported and never will be")
|
||||||
|
case "":
|
||||||
|
return nil, fmt.Errorf("could not find file extension for %s", src)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported file extension: %s", path.Ext(src))
|
||||||
|
}
|
||||||
|
}
|
||||||
1
kitcom/tsgen.go
Normal file
1
kitcom/tsgen.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package main
|
||||||
9
kitcom/tsparser.go
Normal file
9
kitcom/tsparser.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type TypescriptApiParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypescriptApiParser) Parse(sourceFile string) (Api, error) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
12
ts/package.json
Normal file
12
ts/package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "kitten-ipc",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
44
ts/tsconfig.json
Normal file
44
ts/tsconfig.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
// Visit https://aka.ms/tsconfig to read more about this file
|
||||||
|
"compilerOptions": {
|
||||||
|
// File Layout
|
||||||
|
// "rootDir": "./src",
|
||||||
|
// "outDir": "./dist",
|
||||||
|
|
||||||
|
// Environment Settings
|
||||||
|
// See also https://aka.ms/tsconfig/module
|
||||||
|
"module": "nodenext",
|
||||||
|
"target": "esnext",
|
||||||
|
"types": [],
|
||||||
|
// For nodejs:
|
||||||
|
// "lib": ["esnext"],
|
||||||
|
// "types": ["node"],
|
||||||
|
// and npm install -D @types/node
|
||||||
|
|
||||||
|
// Other Outputs
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
|
||||||
|
// Stricter Typechecking Options
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
|
||||||
|
// Style Options
|
||||||
|
// "noImplicitReturns": true,
|
||||||
|
// "noImplicitOverride": true,
|
||||||
|
// "noUnusedLocals": true,
|
||||||
|
// "noUnusedParameters": true,
|
||||||
|
// "noFallthroughCasesInSwitch": true,
|
||||||
|
// "noPropertyAccessFromIndexSignature": true,
|
||||||
|
|
||||||
|
// Recommended Options
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noUncheckedSideEffectImports": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user