Compare commits
12 Commits
41d24fe134
...
d58fd6434f
Author | SHA1 | Date | |
---|---|---|---|
d58fd6434f | |||
0f9733c890 | |||
14643d3cf4 | |||
d49f837b2e | |||
cb19d235c6 | |||
d41d1d8a34 | |||
b6d9c7816c | |||
7345d065b8 | |||
3211e8607d | |||
be3e0fa2c2 | |||
71527eab2b | |||
187d602c91 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,3 +25,4 @@ files/
|
|||||||
target/
|
target/
|
||||||
package/leanote.tar.gz
|
package/leanote.tar.gz
|
||||||
package/leanote/
|
package/leanote/
|
||||||
|
leanote.log
|
11
.travis.yml
11
.travis.yml
@ -7,7 +7,7 @@ services:
|
|||||||
install:
|
install:
|
||||||
- go version
|
- go version
|
||||||
- export PATH=$PATH:$HOME/gopath/bin
|
- export PATH=$PATH:$HOME/gopath/bin
|
||||||
- go get -v github.com/leanote/leanote/app
|
# - go get -v github.com/leanote/leanote/app
|
||||||
- go get -u github.com/revel/cmd/revel
|
- go get -u github.com/revel/cmd/revel
|
||||||
# - ls $GOPATH/src/github.com/revel/
|
# - ls $GOPATH/src/github.com/revel/
|
||||||
# - go get github.com/revel/moudle/revel
|
# - go get github.com/revel/moudle/revel
|
||||||
@ -17,10 +17,16 @@ install:
|
|||||||
- ls
|
- ls
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- wget https://github.com/leanote/leanote/archive/refs/heads/master.zip
|
||||||
|
- unzip master.zip
|
||||||
|
- mv leanote-master leanote
|
||||||
|
- cd leanote
|
||||||
|
|
||||||
- mongo --version
|
- mongo --version
|
||||||
- mongorestore -h localhost -d leanote --dir ./mongodb_backup/leanote_install_data/
|
- mongorestore -h localhost -d leanote --dir ./mongodb_backup/leanote_install_data/
|
||||||
|
|
||||||
- cd $GOPATH/src/github.com/leanote/leanote/sh
|
- cd ./sh
|
||||||
|
# - cd $GOPATH/src/github.com/leanote/leanote/sh
|
||||||
- sh run.sh &
|
- sh run.sh &
|
||||||
|
|
||||||
# gen tmp/main.go, routes/routes.go
|
# gen tmp/main.go, routes/routes.go
|
||||||
@ -29,6 +35,7 @@ script:
|
|||||||
#- go build -o leanote github.com/leanote/leanote/app/tmp
|
#- go build -o leanote github.com/leanote/leanote/app/tmp
|
||||||
# run with port 9000
|
# run with port 9000
|
||||||
#- ./leanote -importPath=github.com/leanote/leanote -runMode=dev -port=9000 &
|
#- ./leanote -importPath=github.com/leanote/leanote -runMode=dev -port=9000 &
|
||||||
|
|
||||||
- sleep 10s;
|
- sleep 10s;
|
||||||
# test
|
# test
|
||||||
- curl http://localhost:9000
|
- curl http://localhost:9000
|
||||||
|
@ -50,7 +50,7 @@ More information about how to install Leanote please see:
|
|||||||
* [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En))
|
* [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En))
|
||||||
* [Mac and Linux](https://github.com/leanote/leanote/wiki/leanote-binary-installation-on-Mac-and-Linux-(En))
|
* [Mac and Linux](https://github.com/leanote/leanote/wiki/leanote-binary-installation-on-Mac-and-Linux-(En))
|
||||||
* Leanote source installation tutorial:
|
* Leanote source installation tutorial:
|
||||||
* [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En))
|
<!-- * [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En)) -->
|
||||||
* [Mac and Linux](https://github.com/leanote/leanote/wiki/Leanote-source-installation-on-Mac-and-Linux-(En))
|
* [Mac and Linux](https://github.com/leanote/leanote/wiki/Leanote-source-installation-on-Mac-and-Linux-(En))
|
||||||
|
|
||||||
## 4. Documentation
|
## 4. Documentation
|
||||||
@ -145,7 +145,7 @@ Leanote云笔记产品包括: Leanote Web & Server(即本仓库), 桌面客户
|
|||||||
* [Windows](https://github.com/leanote/leanote/wiki/Leanote%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B---Windows)
|
* [Windows](https://github.com/leanote/leanote/wiki/Leanote%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B---Windows)
|
||||||
* [Mac, Linux](https://github.com/leanote/leanote/wiki/leanote%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B)
|
* [Mac, Linux](https://github.com/leanote/leanote/wiki/leanote%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B)
|
||||||
* Leanote源码详细安装教程:
|
* Leanote源码详细安装教程:
|
||||||
* [Windows](https://github.com/leanote/leanote/wiki/Leanote-%E6%BA%90%E7%A0%81%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B----Windows)
|
<!-- * [Windows](https://github.com/leanote/leanote/wiki/Leanote-%E6%BA%90%E7%A0%81%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B----Windows) -->
|
||||||
* [Mac, Linux](https://github.com/leanote/leanote/wiki/leanote%E5%BC%80%E5%8F%91%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B)
|
* [Mac, Linux](https://github.com/leanote/leanote/wiki/leanote%E5%BC%80%E5%8F%91%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B)
|
||||||
|
|
||||||
## 4. 相关文档
|
## 4. 相关文档
|
||||||
|
9
app/cmd/README.md
Normal file
9
app/cmd/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
全部代码来自https://github.com/revel/cmd
|
||||||
|
|
||||||
|
因为要改parse2, 所以改只要一点点代码
|
||||||
|
harness/
|
||||||
|
build.go 只要gensource, 其它的先return
|
||||||
|
main.go 改动很小
|
||||||
|
build.go 改动很小
|
||||||
|
parser2/
|
||||||
|
source_processors.go 改了 fsWalk 过滤掉 public, files, build 等文件夹
|
270
app/cmd/build.go
Normal file
270
app/cmd/build.go
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"github.com/leanote/leanote/app/cmd/harness" // 只改了这个
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdBuild = &Command{
|
||||||
|
UsageLine: "revel build [-r [run mode]] [import path] [target path] ",
|
||||||
|
Short: "build a Revel application (e.g. for deployment)",
|
||||||
|
Long: `
|
||||||
|
Build the Revel web application named by the given import path.
|
||||||
|
This allows it to be deployed and run on a machine that lacks a Go installation.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
revel build github.com/revel/examples/chat /tmp/chat
|
||||||
|
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdBuild.RunWith = buildApp
|
||||||
|
cmdBuild.UpdateConfig = updateBuildConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// The update config updates the configuration command so that it can run
|
||||||
|
func updateBuildConfig(c *model.CommandConfig, args []string) bool {
|
||||||
|
c.Index = model.BUILD
|
||||||
|
if c.Build.TargetPath == "" {
|
||||||
|
c.Build.TargetPath = "target"
|
||||||
|
}
|
||||||
|
if len(args) == 0 && c.Build.ImportPath != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// If arguments were passed in then there must be two
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Build.ImportPath = args[0]
|
||||||
|
c.Build.TargetPath = args[1]
|
||||||
|
if len(args) > 2 {
|
||||||
|
c.Build.Mode = args[2]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main entry point to build application from command line
|
||||||
|
func buildApp(c *model.CommandConfig) (err error) {
|
||||||
|
|
||||||
|
appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
|
||||||
|
if len(c.Build.Mode) > 0 {
|
||||||
|
mode = c.Build.Mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert target to absolute path
|
||||||
|
c.Build.TargetPath, _ = filepath.Abs(destPath)
|
||||||
|
c.Build.Mode = mode
|
||||||
|
c.Build.ImportPath = appImportPath
|
||||||
|
|
||||||
|
revel_paths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = buildSafetyCheck(destPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the application can be built, this generates the main file
|
||||||
|
app, err := harness.Build(c, revel_paths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Copy files
|
||||||
|
// Included are:
|
||||||
|
// - run scripts
|
||||||
|
// - binary
|
||||||
|
// - revel
|
||||||
|
// - app
|
||||||
|
|
||||||
|
return // 改了这里
|
||||||
|
|
||||||
|
packageFolders, err := buildCopyFiles(c, app, revel_paths)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = buildCopyModules(c, revel_paths, packageFolders, app)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = buildWriteScripts(c, app)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the files to the target
|
||||||
|
func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model.RevelContainer) (packageFolders []string, err error) {
|
||||||
|
appImportPath, destPath := c.ImportPath, c.Build.TargetPath
|
||||||
|
|
||||||
|
// Revel and the app are in a directory structure mirroring import path
|
||||||
|
srcPath := filepath.Join(destPath, "src")
|
||||||
|
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
||||||
|
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
|
||||||
|
if err = utils.CopyFile(destBinaryPath, filepath.Join(revel_paths.BasePath, app.BinaryPath)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.MustChmod(destBinaryPath, 0755)
|
||||||
|
|
||||||
|
// Copy the templates from the revel
|
||||||
|
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the folders to be packaged
|
||||||
|
packageFolders = strings.Split(revel_paths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
|
||||||
|
for i, p := range packageFolders {
|
||||||
|
// Clean spaces, reformat slash to filesystem
|
||||||
|
packageFolders[i] = filepath.FromSlash(strings.TrimSpace(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Build.CopySource {
|
||||||
|
err = utils.CopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel_paths.BasePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, folder := range packageFolders {
|
||||||
|
err = utils.CopyDir(
|
||||||
|
filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
|
||||||
|
filepath.Join(revel_paths.BasePath, folder),
|
||||||
|
nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on the section copy over the build modules
|
||||||
|
func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) {
|
||||||
|
destPath := filepath.Join(c.Build.TargetPath, "src")
|
||||||
|
// Find all the modules used and copy them over.
|
||||||
|
config := revel_paths.Config.Raw()
|
||||||
|
|
||||||
|
// We should only copy over the section of options what the build is targeted for
|
||||||
|
// We will default to prod
|
||||||
|
moduleImportList := []string{}
|
||||||
|
for _, section := range config.Sections() {
|
||||||
|
// If the runmode is defined we will only import modules defined for that run mode
|
||||||
|
if c.Build.Mode != "" && c.Build.Mode != section {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
options, _ := config.SectionOptions(section)
|
||||||
|
for _, key := range options {
|
||||||
|
if !strings.HasPrefix(key, "module.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
moduleImportPath, _ := config.String(section, key)
|
||||||
|
if moduleImportPath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
moduleImportList = append(moduleImportList, moduleImportPath)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the the paths for each of the modules
|
||||||
|
for _, importPath := range moduleImportList {
|
||||||
|
fsPath := app.PackagePathMap[importPath]
|
||||||
|
utils.Logger.Info("Copy files ", "to", filepath.Join(destPath, importPath), "from", fsPath)
|
||||||
|
if c.Build.CopySource {
|
||||||
|
err = utils.CopyDir(filepath.Join(destPath, importPath), fsPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, folder := range packageFolders {
|
||||||
|
err = utils.CopyDir(
|
||||||
|
filepath.Join(destPath, importPath, folder),
|
||||||
|
filepath.Join(fsPath, folder),
|
||||||
|
nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the run scripts for the build
|
||||||
|
func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
|
||||||
|
tmplData := map[string]interface{}{
|
||||||
|
"BinName": filepath.Base(app.BinaryPath),
|
||||||
|
"ImportPath": c.Build.ImportPath,
|
||||||
|
"Mode": c.Build.Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = utils.GenerateTemplate(
|
||||||
|
filepath.Join(c.Build.TargetPath, "run.sh"),
|
||||||
|
PACKAGE_RUN_SH,
|
||||||
|
tmplData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.MustChmod(filepath.Join(c.Build.TargetPath, "run.sh"), 0755)
|
||||||
|
err = utils.GenerateTemplate(
|
||||||
|
filepath.Join(c.Build.TargetPath, "run.bat"),
|
||||||
|
PACKAGE_RUN_BAT,
|
||||||
|
tmplData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Your application has been built in:", c.Build.TargetPath)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks to see if the target folder exists and can be created
|
||||||
|
func buildSafetyCheck(destPath string) error {
|
||||||
|
|
||||||
|
// First, verify that it is either already empty or looks like a previous
|
||||||
|
// build (to avoid clobbering anything)
|
||||||
|
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
|
||||||
|
return utils.NewBuildError("Abort: %s exists and does not look like a build directory.", "path", destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return utils.NewBuildIfError(err, "Remove all error", "path", destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||||
|
return utils.NewBuildIfError(err, "MkDir all error", "path", destPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const PACKAGE_RUN_SH = `#!/bin/sh
|
||||||
|
|
||||||
|
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||||
|
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
|
||||||
|
`
|
||||||
|
const PACKAGE_RUN_BAT = `@echo off
|
||||||
|
|
||||||
|
{{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}
|
||||||
|
`
|
4
app/cmd/gen_tmp.sh
Normal file
4
app/cmd/gen_tmp.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||||
|
cd $SCRIPTPATH
|
||||||
|
go run . build -v ../../ ./tmptmp
|
||||||
|
rm -rf ./tmptmp
|
219
app/cmd/harness/app.go
Normal file
219
app/cmd/harness/app.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package harness
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App contains the configuration for running a Revel app. (Not for the app itself)
|
||||||
|
// Its only purpose is constructing the command to execute.
|
||||||
|
type App struct {
|
||||||
|
BinaryPath string // Path to the app executable
|
||||||
|
Port int // Port to pass as a command line argument.
|
||||||
|
cmd AppCmd // The last cmd returned.
|
||||||
|
PackagePathMap map[string]string // Package to directory path map
|
||||||
|
Paths *model.RevelContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApp returns app instance with binary path in it
|
||||||
|
func NewApp(binPath string, paths *model.RevelContainer, packagePathMap map[string]string) *App {
|
||||||
|
return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort, PackagePathMap:packagePathMap}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmd returns a command to run the app server using the current configuration.
|
||||||
|
func (a *App) Cmd(runMode string) AppCmd {
|
||||||
|
a.cmd = NewAppCmd(a.BinaryPath, a.Port, runMode, a.Paths)
|
||||||
|
return a.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill the last app command returned.
|
||||||
|
func (a *App) Kill() {
|
||||||
|
a.cmd.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppCmd manages the running of a Revel app server.
|
||||||
|
// It requires revel.Init to have been called previously.
|
||||||
|
type AppCmd struct {
|
||||||
|
*exec.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppCmd returns the AppCmd with parameters initialized for running app
|
||||||
|
func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelContainer) AppCmd {
|
||||||
|
cmd := exec.Command(binPath,
|
||||||
|
fmt.Sprintf("-port=%d", port),
|
||||||
|
fmt.Sprintf("-importPath=%s", paths.ImportPath),
|
||||||
|
fmt.Sprintf("-runMode=%s", runMode))
|
||||||
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||||
|
return AppCmd{cmd}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the app server, and wait until it is ready to serve requests.
|
||||||
|
func (cmd AppCmd) Start(c *model.CommandConfig) error {
|
||||||
|
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c, &bytes.Buffer{}}
|
||||||
|
cmd.Stdout = listeningWriter
|
||||||
|
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath)
|
||||||
|
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args, "dir", cmd.Dir, "env", cmd.Env)
|
||||||
|
if err := cmd.Cmd.Start(); err != nil {
|
||||||
|
utils.Logger.Fatal("Error running:", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case exitState := <-cmd.waitChan():
|
||||||
|
fmt.Println("Startup failure view previous messages, \n Proxy is listening :", c.Run.Port)
|
||||||
|
err := utils.NewError("", "Revel Run Error", "starting your application there was an exception. See terminal output, " + exitState, "")
|
||||||
|
// TODO pretiffy command line output
|
||||||
|
// err.MetaError = listeningWriter.getLastOutput()
|
||||||
|
return err
|
||||||
|
|
||||||
|
case <-time.After(60 * time.Second):
|
||||||
|
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||||
|
utils.Logger.Error("Killing revel server process did not respond after wait timeout.", "processid", cmd.Process.Pid)
|
||||||
|
cmd.Kill()
|
||||||
|
return errors.New("revel/harness: app timed out")
|
||||||
|
|
||||||
|
case <-listeningWriter.notifyReady:
|
||||||
|
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the app server inline. Never returns.
|
||||||
|
func (cmd AppCmd) Run(c *model.CommandConfig) {
|
||||||
|
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath)
|
||||||
|
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||||
|
if err := cmd.Cmd.Run(); err != nil {
|
||||||
|
utils.Logger.Fatal("Error running:", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill terminates the app server if it's running.
|
||||||
|
func (cmd AppCmd) Kill() {
|
||||||
|
|
||||||
|
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
||||||
|
// Windows appears to send the kill to all threads, shutting down the
|
||||||
|
// server before this can, this check will ensure the process is still running
|
||||||
|
if _, err := os.FindProcess(int(cmd.Process.Pid)); err != nil {
|
||||||
|
// Server has already exited
|
||||||
|
utils.Logger.Info("Server not running revel server pid", "pid", cmd.Process.Pid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the shutdown channel
|
||||||
|
waitMutex := &sync.WaitGroup{}
|
||||||
|
waitMutex.Add(1)
|
||||||
|
ch := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
waitMutex.Done()
|
||||||
|
s, err := cmd.Process.Wait()
|
||||||
|
defer func() {
|
||||||
|
ch <- true
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Info("Wait failed for process ", "error", err)
|
||||||
|
}
|
||||||
|
if s != nil {
|
||||||
|
utils.Logger.Info("Revel App exited", "state", s.String())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Wait for the channel to begin waiting
|
||||||
|
waitMutex.Wait()
|
||||||
|
|
||||||
|
// Send an interrupt signal to allow for a graceful shutdown
|
||||||
|
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
|
||||||
|
var err error
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
// os.Interrupt is not available on windows
|
||||||
|
err = cmd.Process.Signal(os.Interrupt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Info(
|
||||||
|
"Revel app already exited.",
|
||||||
|
"processid", cmd.Process.Pid, "error", err,
|
||||||
|
"killerror", cmd.Process.Kill())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Use a timer to ensure that the process exits
|
||||||
|
utils.Logger.Info("Waiting to exit")
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
return
|
||||||
|
case <-time.After(60 * time.Second):
|
||||||
|
// Kill the process
|
||||||
|
utils.Logger.Error(
|
||||||
|
"Revel app failed to exit in 60 seconds - killing.",
|
||||||
|
"processid", cmd.Process.Pid,
|
||||||
|
"killerror", cmd.Process.Kill())
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Info("Done Waiting to exit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a channel that is notified when Wait() returns.
|
||||||
|
func (cmd AppCmd) waitChan() <-chan string {
|
||||||
|
ch := make(chan string, 1)
|
||||||
|
go func() {
|
||||||
|
_ = cmd.Wait()
|
||||||
|
state := cmd.ProcessState
|
||||||
|
exitStatus := " unknown "
|
||||||
|
if state != nil {
|
||||||
|
exitStatus = state.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- exitStatus
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// A io.Writer that copies to the destination, and listens for "Revel engine is listening on.."
|
||||||
|
// in the stream. (Which tells us when the revel server has finished starting up)
|
||||||
|
// This is super ghetto, but by far the simplest thing that should work.
|
||||||
|
type startupListeningWriter struct {
|
||||||
|
dest io.Writer
|
||||||
|
notifyReady chan bool
|
||||||
|
c *model.CommandConfig
|
||||||
|
buffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes to this output stream
|
||||||
|
func (w *startupListeningWriter) Write(p []byte) (int, error) {
|
||||||
|
if w.notifyReady != nil && bytes.Contains(p, []byte("Revel engine is listening on")) {
|
||||||
|
w.notifyReady <- true
|
||||||
|
w.notifyReady = nil
|
||||||
|
}
|
||||||
|
if w.c.HistoricMode {
|
||||||
|
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening on")) {
|
||||||
|
w.notifyReady <- true
|
||||||
|
w.notifyReady = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if w.notifyReady != nil {
|
||||||
|
w.buffer.Write(p)
|
||||||
|
}
|
||||||
|
return w.dest.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the cleaned output from the response
|
||||||
|
// TODO clean the response more
|
||||||
|
func (w *startupListeningWriter) getLastOutput() string {
|
||||||
|
return w.buffer.String()
|
||||||
|
}
|
||||||
|
|
566
app/cmd/harness/build.go
Normal file
566
app/cmd/harness/build.go
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package harness
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/leanote/leanote/app/cmd/parser2"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/parser"
|
||||||
|
_ "github.com/revel/cmd/parser"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
|
||||||
|
|
||||||
|
type ByString []*model.TypeInfo
|
||||||
|
|
||||||
|
func (c ByString) Len() int {
|
||||||
|
return len(c)
|
||||||
|
}
|
||||||
|
func (c ByString) Swap(i, j int) {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
||||||
|
func (c ByString) Less(i, j int) bool {
|
||||||
|
return c[i].String() < c[j].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the app:
|
||||||
|
// 1. Generate the the main.go file.
|
||||||
|
// 2. Run the appropriate "go build" command.
|
||||||
|
// Requires that revel.Init has been called previously.
|
||||||
|
// Returns the path to the built binary, and an error if there was a problem building it.
|
||||||
|
func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err error) {
|
||||||
|
// First, clear the generated files (to avoid them messing with ProcessSource).
|
||||||
|
cleanSource(paths, "tmp", "routes")
|
||||||
|
|
||||||
|
var sourceInfo *model.SourceInfo
|
||||||
|
|
||||||
|
if c.HistoricBuildMode {
|
||||||
|
sourceInfo, err = parser.ProcessSource(paths)
|
||||||
|
} else {
|
||||||
|
sourceInfo, err = parser2.ProcessSource(paths)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the db.import to the import paths.
|
||||||
|
if dbImportPath, found := paths.Config.String("db.import"); found {
|
||||||
|
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, strings.Split(dbImportPath, ",")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort controllers so that file generation is reproducible
|
||||||
|
controllers := sourceInfo.ControllerSpecs()
|
||||||
|
sort.Stable(ByString(controllers))
|
||||||
|
|
||||||
|
// Generate two source files.
|
||||||
|
templateArgs := map[string]interface{}{
|
||||||
|
"ImportPath": paths.ImportPath,
|
||||||
|
"Controllers": controllers,
|
||||||
|
"ValidationKeys": sourceInfo.ValidationKeys,
|
||||||
|
"ImportPaths": calcImportAliases(sourceInfo),
|
||||||
|
"TestSuites": sourceInfo.TestSuites(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate code for the main, run and routes file.
|
||||||
|
// The run file allows external programs to launch and run the application
|
||||||
|
// without being the main thread
|
||||||
|
cleanSource(paths, "tmp", "routes")
|
||||||
|
|
||||||
|
if err = genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = genSource(paths, filepath.Join("tmp", "run"), "run.go", RevelRunTemplate, templateArgs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Warn("gen tmp/main.go, tmp/run/run.go, routes/routes.go success!!")
|
||||||
|
|
||||||
|
return // 改了这里
|
||||||
|
|
||||||
|
// Read build config.
|
||||||
|
buildTags := paths.Config.StringDefault("build.tags", "")
|
||||||
|
|
||||||
|
// Build the user program (all code under app).
|
||||||
|
// It relies on the user having "go" installed.
|
||||||
|
goPath, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Go executable not found in PATH.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary path is a combination of target/app directory, app's import path and its name.
|
||||||
|
binName := filepath.Join("target", "app", paths.ImportPath, filepath.Base(paths.BasePath))
|
||||||
|
|
||||||
|
// Change binary path for Windows build
|
||||||
|
goos := runtime.GOOS
|
||||||
|
if goosEnv := os.Getenv("GOOS"); goosEnv != "" {
|
||||||
|
goos = goosEnv
|
||||||
|
}
|
||||||
|
if goos == "windows" {
|
||||||
|
binName += ".exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
gotten := make(map[string]struct{})
|
||||||
|
contains := func(s []string, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.GoModFlags) > 0 {
|
||||||
|
for _, gomod := range c.GoModFlags {
|
||||||
|
goModCmd := exec.Command(goPath, append([]string{"mod"}, strings.Split(gomod, " ")...)...)
|
||||||
|
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
|
||||||
|
output, err := goModCmd.CombinedOutput()
|
||||||
|
utils.Logger.Info("Gomod applied ", "output", string(output))
|
||||||
|
|
||||||
|
// If the build succeeded, we're done.
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Gomod Failed continuing ", "error", err, "output", string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
appVersion := getAppVersion(paths)
|
||||||
|
if appVersion == "" {
|
||||||
|
appVersion = "noVersionProvided"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTime := time.Now().UTC().Format(time.RFC3339)
|
||||||
|
versionLinkerFlags := fmt.Sprintf("-X '%s/app.AppVersion=%s' -X '%s/app.BuildTime=%s'",
|
||||||
|
paths.ImportPath, appVersion, paths.ImportPath, buildTime)
|
||||||
|
|
||||||
|
// Append any build flags specified, they will override existing flags
|
||||||
|
flags := []string{}
|
||||||
|
if len(c.BuildFlags) == 0 {
|
||||||
|
flags = []string{
|
||||||
|
"build",
|
||||||
|
"-ldflags", versionLinkerFlags,
|
||||||
|
"-tags", buildTags,
|
||||||
|
"-o", binName}
|
||||||
|
} else {
|
||||||
|
if !contains(c.BuildFlags, "build") {
|
||||||
|
flags = []string{"build"}
|
||||||
|
}
|
||||||
|
if !contains(flags, "-ldflags") {
|
||||||
|
ldflags := "-ldflags= " + versionLinkerFlags
|
||||||
|
// Add user defined build flags
|
||||||
|
for i := range c.BuildFlags {
|
||||||
|
ldflags += " -X '" + c.BuildFlags[i] + "'"
|
||||||
|
}
|
||||||
|
flags = append(flags, ldflags)
|
||||||
|
}
|
||||||
|
if !contains(flags, "-tags") && buildTags != "" {
|
||||||
|
flags = append(flags, "-tags", buildTags)
|
||||||
|
}
|
||||||
|
if !contains(flags, "-o") {
|
||||||
|
flags = append(flags, "-o", binName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: It's not applicable for filepath.* usage
|
||||||
|
flags = append(flags, path.Join(paths.ImportPath, "app", "tmp"))
|
||||||
|
|
||||||
|
buildCmd := exec.Command(goPath, flags...)
|
||||||
|
if !c.Vendored {
|
||||||
|
// This is Go main path
|
||||||
|
gopath := c.GoPath
|
||||||
|
for _, o := range paths.ModulePathMap {
|
||||||
|
gopath += string(filepath.ListSeparator) + o.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
buildCmd.Env = append(os.Environ(),
|
||||||
|
"GOPATH=" + gopath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
utils.CmdInit(buildCmd, !c.Vendored, c.AppPath)
|
||||||
|
|
||||||
|
utils.Logger.Info("Exec:", "args", buildCmd.Args, "working dir", buildCmd.Dir)
|
||||||
|
output, err := buildCmd.CombinedOutput()
|
||||||
|
|
||||||
|
// If the build succeeded, we're done.
|
||||||
|
if err == nil {
|
||||||
|
utils.Logger.Info("Build successful continuing")
|
||||||
|
return NewApp(binName, paths, sourceInfo.PackageMap), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since there was an error, capture the output in case we need to report it
|
||||||
|
stOutput := string(output)
|
||||||
|
utils.Logger.Infof("Got error on build of app %s", stOutput)
|
||||||
|
|
||||||
|
// See if it was an import error that we can go get.
|
||||||
|
matches := importErrorPattern.FindAllStringSubmatch(stOutput, -1)
|
||||||
|
utils.Logger.Info("Build failed checking for missing imports", "message", stOutput, "missing_imports", len(matches))
|
||||||
|
if matches == nil {
|
||||||
|
utils.Logger.Info("Build failed no missing imports", "message", stOutput)
|
||||||
|
return nil, newCompileError(paths, output)
|
||||||
|
}
|
||||||
|
utils.Logger.Warn("Detected missing packages, importing them", "packages", len(matches))
|
||||||
|
for _, match := range matches {
|
||||||
|
// Ensure we haven't already tried to go get it.
|
||||||
|
pkgName := match[1]
|
||||||
|
utils.Logger.Info("Trying to import ", "package", pkgName)
|
||||||
|
if _, alreadyTried := gotten[pkgName]; alreadyTried {
|
||||||
|
utils.Logger.Error("Failed to import ", "package", pkgName)
|
||||||
|
return nil, newCompileError(paths, output)
|
||||||
|
}
|
||||||
|
gotten[pkgName] = struct{}{}
|
||||||
|
if err := c.PackageResolver(pkgName); err != nil {
|
||||||
|
utils.Logger.Error("Unable to resolve package", "package", pkgName, "error", err)
|
||||||
|
return nil, newCompileError(paths, []byte(err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success getting the import, attempt to build again.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove this unreachable code and document it
|
||||||
|
utils.Logger.Fatal("Not reachable")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to define a version string for the compiled app
|
||||||
|
// The following is tried (first match returns):
|
||||||
|
// - Read a version explicitly specified in the APP_VERSION environment
|
||||||
|
// variable
|
||||||
|
// - Read the output of "git describe" if the source is in a git repository
|
||||||
|
// If no version can be determined, an empty string is returned.
|
||||||
|
func getAppVersion(paths *model.RevelContainer) string {
|
||||||
|
if version := os.Getenv("APP_VERSION"); version != "" {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the git binary
|
||||||
|
if gitPath, err := exec.LookPath("git"); err == nil {
|
||||||
|
// Check for the .git directory
|
||||||
|
gitDir := filepath.Join(paths.BasePath, ".git")
|
||||||
|
info, err := os.Stat(gitDir)
|
||||||
|
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
gitCmd := exec.Command(gitPath, "--git-dir=" + gitDir, "--work-tree=" + paths.BasePath, "describe", "--always", "--dirty")
|
||||||
|
utils.Logger.Info("Exec:", "args", gitCmd.Args)
|
||||||
|
output, err := gitCmd.Output()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Cannot determine git repository version:", "error", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "git-" + strings.TrimSpace(string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanSource(paths *model.RevelContainer, dirs ...string) {
|
||||||
|
for _, dir := range dirs {
|
||||||
|
cleanDir(paths, dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanDir(paths *model.RevelContainer, dir string) {
|
||||||
|
utils.Logger.Info("Cleaning dir ", "dir", dir)
|
||||||
|
tmpPath := filepath.Join(paths.AppPath, dir)
|
||||||
|
f, err := os.Open(tmpPath)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
utils.Logger.Error("Failed to clean dir:", "error", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
_ = f.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
infos, err := f.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
utils.Logger.Fatal("Failed to clean dir:", "error", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, info := range infos {
|
||||||
|
pathName := filepath.Join(tmpPath, info.Name())
|
||||||
|
if info.IsDir() {
|
||||||
|
err := os.RemoveAll(pathName)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Failed to remove dir:", "error", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := os.Remove(pathName)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Failed to remove file:", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// genSource renders the given template to produce source code, which it writes
|
||||||
|
// to the given directory and file.
|
||||||
|
func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) error {
|
||||||
|
|
||||||
|
return utils.GenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks through all the method args and returns a set of unique import paths
|
||||||
|
// that cover all the method arg types.
|
||||||
|
// Additionally, assign package aliases when necessary to resolve ambiguity.
|
||||||
|
func calcImportAliases(src *model.SourceInfo) map[string]string {
|
||||||
|
aliases := make(map[string]string)
|
||||||
|
typeArrays := [][]*model.TypeInfo{src.ControllerSpecs(), src.TestSuites()}
|
||||||
|
for _, specs := range typeArrays {
|
||||||
|
for _, spec := range specs {
|
||||||
|
addAlias(aliases, spec.ImportPath, spec.PackageName)
|
||||||
|
|
||||||
|
for _, methSpec := range spec.MethodSpecs {
|
||||||
|
for _, methArg := range methSpec.Args {
|
||||||
|
if methArg.ImportPath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addAlias(aliases, methArg.ImportPath, methArg.TypeExpr.PkgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the "InitImportPaths", with alias "_"
|
||||||
|
for _, importPath := range src.InitImportPaths {
|
||||||
|
if _, ok := aliases[importPath]; !ok {
|
||||||
|
aliases[importPath] = "_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds an alias to the map of alias names
|
||||||
|
func addAlias(aliases map[string]string, importPath, pkgName string) {
|
||||||
|
alias, ok := aliases[importPath]
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
alias = makePackageAlias(aliases, pkgName)
|
||||||
|
aliases[importPath] = alias
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a package alias
|
||||||
|
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
||||||
|
i := 0
|
||||||
|
alias := pkgName
|
||||||
|
for containsValue(aliases, alias) || alias == "revel" {
|
||||||
|
alias = fmt.Sprintf("%s%d", pkgName, i)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if this value is in the map
|
||||||
|
func containsValue(m map[string]string, val string) bool {
|
||||||
|
for _, v := range m {
|
||||||
|
if v == val {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the output of the "go build" command.
|
||||||
|
// Return a detailed Error.
|
||||||
|
func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceError {
|
||||||
|
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
||||||
|
FindSubmatch(output)
|
||||||
|
if errorMatch == nil {
|
||||||
|
errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch(output)
|
||||||
|
|
||||||
|
if errorMatch == nil {
|
||||||
|
utils.Logger.Error("Failed to parse build errors", "error", string(output))
|
||||||
|
return &utils.SourceError{
|
||||||
|
SourceType: "Go code",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Description: "See console for build error.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMatch = append(errorMatch, errorMatch[3])
|
||||||
|
|
||||||
|
utils.Logger.Error("Build errors", "errors", string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
findInPaths := func(relFilename string) string {
|
||||||
|
// Extract the paths from the gopaths, and search for file there first
|
||||||
|
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||||
|
for _, gp := range gopaths {
|
||||||
|
newPath := filepath.Join(gp, "src", paths.ImportPath, relFilename)
|
||||||
|
println(newPath)
|
||||||
|
if utils.Exists(newPath) {
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPath, _ := filepath.Abs(relFilename)
|
||||||
|
utils.Logger.Warn("Could not find in GO path", "file", relFilename)
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Read the source for the offending file.
|
||||||
|
var (
|
||||||
|
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
||||||
|
absFilename = findInPaths(relFilename)
|
||||||
|
line, _ = strconv.Atoi(string(errorMatch[2]))
|
||||||
|
description = string(errorMatch[4])
|
||||||
|
compileError = &utils.SourceError{
|
||||||
|
SourceType: "Go code",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Path: relFilename,
|
||||||
|
Description: description,
|
||||||
|
Line: line,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
errorLink := paths.Config.StringDefault("error.link", "")
|
||||||
|
|
||||||
|
if errorLink != "" {
|
||||||
|
compileError.SetLink(errorLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStr, err := utils.ReadLines(absFilename)
|
||||||
|
if err != nil {
|
||||||
|
compileError.MetaError = absFilename + ": " + err.Error()
|
||||||
|
utils.Logger.Info("Unable to readlines " + compileError.MetaError, "error", err)
|
||||||
|
return compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
compileError.SourceLines = fileStr
|
||||||
|
return compileError
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevelMainTemplate template for app/tmp/run/run.go
|
||||||
|
const RevelRunTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||||
|
// This file is the run file for Revel.
|
||||||
|
// It registers all the controllers and provides details for the Revel server engine to
|
||||||
|
// properly inject parameters directly into the action endpoints.
|
||||||
|
package run
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"github.com/revel/revel"{{range $k, $v := $.ImportPaths}}
|
||||||
|
{{$v}} "{{$k}}"{{end}}
|
||||||
|
"github.com/revel/revel/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// So compiler won't complain if the generated code doesn't reference reflect package...
|
||||||
|
_ = reflect.Invalid
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register and run the application
|
||||||
|
func Run(port int) {
|
||||||
|
Register()
|
||||||
|
revel.Run(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register all the controllers
|
||||||
|
func Register() {
|
||||||
|
revel.AppLog.Info("Running revel server")
|
||||||
|
{{range $i, $c := .Controllers}}
|
||||||
|
revel.RegisterController((*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),
|
||||||
|
[]*revel.MethodType{
|
||||||
|
{{range .MethodSpecs}}&revel.MethodType{
|
||||||
|
Name: "{{.Name}}",
|
||||||
|
Args: []*revel.MethodArg{ {{range .Args}}
|
||||||
|
&revel.MethodArg{Name: "{{.Name}}", Type: reflect.TypeOf((*{{index $.ImportPaths .ImportPath | .TypeExpr.TypeName}})(nil)) },{{end}}
|
||||||
|
},
|
||||||
|
RenderArgNames: map[int][]string{ {{range .RenderCalls}}
|
||||||
|
{{.Line}}: []string{ {{range .Names}}
|
||||||
|
"{{.}}",{{end}}
|
||||||
|
},{{end}}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{{end}}
|
||||||
|
})
|
||||||
|
{{end}}
|
||||||
|
revel.DefaultValidationKeys = map[string]map[int]string{ {{range $path, $lines := .ValidationKeys}}
|
||||||
|
"{{$path}}": { {{range $line, $key := $lines}}
|
||||||
|
{{$line}}: "{{$key}}",{{end}}
|
||||||
|
},{{end}}
|
||||||
|
}
|
||||||
|
testing.TestSuites = []interface{}{ {{range .TestSuites}}
|
||||||
|
(*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),{{end}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||||
|
// This file is the main file for Revel.
|
||||||
|
// It registers all the controllers and provides details for the Revel server engine to
|
||||||
|
// properly inject parameters directly into the action endpoints.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"{{.ImportPath}}/app/tmp/run"
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
runMode *string = flag.String("runMode", "", "Run mode.")
|
||||||
|
port *int = flag.Int("port", 0, "By default, read from app.conf")
|
||||||
|
importPath *string = flag.String("importPath", "", "Go Import Path for the app.")
|
||||||
|
srcPath *string = flag.String("srcPath", "", "Path to the source root.")
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
revel.Init(*runMode, *importPath, *srcPath)
|
||||||
|
run.Run(*port)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// RevelRoutesTemplate template for app/conf/routes
|
||||||
|
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||||
|
// This file provides a way of creating URL's based on all the actions
|
||||||
|
// found in all the controllers.
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import "github.com/revel/revel"
|
||||||
|
|
||||||
|
{{range $i, $c := .Controllers}}
|
||||||
|
type t{{.StructName}} struct {}
|
||||||
|
var {{.StructName}} t{{.StructName}}
|
||||||
|
|
||||||
|
{{range .MethodSpecs}}
|
||||||
|
func (_ t{{$c.StructName}}) {{.Name}}({{range .Args}}
|
||||||
|
{{.Name}} {{if .ImportPath}}interface{}{{else}}{{.TypeExpr.TypeName ""}}{{end}},{{end}}
|
||||||
|
) string {
|
||||||
|
args := make(map[string]string)
|
||||||
|
{{range .Args}}
|
||||||
|
revel.Unbind(args, "{{.Name}}", {{.Name}}){{end}}
|
||||||
|
return revel.MainRouter.Reverse("{{$c.StructName}}.{{.Name}}", args).URL
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
`
|
411
app/cmd/harness/harness.go
Normal file
411
app/cmd/harness/harness.go
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package harness for a Revel Framework.
|
||||||
|
//
|
||||||
|
// It has a following responsibilities:
|
||||||
|
// 1. Parse the user program, generating a main.go file that registers
|
||||||
|
// controller classes and starts the user's server.
|
||||||
|
// 2. Build and run the user program. Show compile errors.
|
||||||
|
// 3. Monitor the user source and re-build / restart the program when necessary.
|
||||||
|
//
|
||||||
|
// Source files are generated in the app/tmp directory.
|
||||||
|
package harness
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"go/build"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"github.com/revel/cmd/watcher"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
doNotWatch = []string{"tmp", "views", "routes"}
|
||||||
|
|
||||||
|
lastRequestHadError int32
|
||||||
|
)
|
||||||
|
|
||||||
|
// Harness reverse proxies requests to the application server.
|
||||||
|
// It builds / runs / rebuilds / restarts the server when code is changed.
|
||||||
|
type Harness struct {
|
||||||
|
app *App // The application
|
||||||
|
useProxy bool // True if proxy is in use
|
||||||
|
serverHost string // The proxy server host
|
||||||
|
port int // The proxy serber port
|
||||||
|
proxy *httputil.ReverseProxy // The proxy
|
||||||
|
watcher *watcher.Watcher // The file watched
|
||||||
|
mutex *sync.Mutex // A mutex to prevent concurrent updates
|
||||||
|
paths *model.RevelContainer // The Revel container
|
||||||
|
config *model.CommandConfig // The configuration
|
||||||
|
runMode string // The runmode the harness is running in
|
||||||
|
isError bool // True if harness is in error state
|
||||||
|
ranOnce bool // True app compiled once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) {
|
||||||
|
// Render error here
|
||||||
|
// Grab the template from three places
|
||||||
|
// 1) Application/views/errors
|
||||||
|
// 2) revel_home/views/errors
|
||||||
|
// 3) views/errors
|
||||||
|
if err == nil {
|
||||||
|
utils.Logger.Panic("Caller passed in a nil error")
|
||||||
|
}
|
||||||
|
templateSet := template.New("__root__")
|
||||||
|
seekViewOnPath := func(view string) (path string) {
|
||||||
|
path = filepath.Join(h.paths.ViewsPath, "errors", view)
|
||||||
|
if !utils.Exists(path) {
|
||||||
|
path = filepath.Join(h.paths.RevelPath, "templates", "errors", view)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Unable to read template file", path)
|
||||||
|
}
|
||||||
|
_, err = templateSet.New("errors/" + view).Parse(string(data))
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Unable to parse template file", path)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := []string{seekViewOnPath("500.html"), seekViewOnPath("500-dev.html")}
|
||||||
|
if !utils.Exists(target[0]) {
|
||||||
|
fmt.Fprintf(iw, "Target template not found not found %s<br />\n", target[0])
|
||||||
|
fmt.Fprintf(iw, "An error ocurred %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var revelError *utils.SourceError
|
||||||
|
switch e := err.(type) {
|
||||||
|
case *utils.SourceError:
|
||||||
|
revelError = e
|
||||||
|
case error:
|
||||||
|
revelError = &utils.SourceError{
|
||||||
|
Title: "Server Error",
|
||||||
|
Description: e.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if revelError == nil {
|
||||||
|
panic("no error provided")
|
||||||
|
}
|
||||||
|
viewArgs := map[string]interface{}{}
|
||||||
|
viewArgs["RunMode"] = h.paths.RunMode
|
||||||
|
viewArgs["DevMode"] = h.paths.DevMode
|
||||||
|
viewArgs["Error"] = revelError
|
||||||
|
|
||||||
|
// Render the template from the file
|
||||||
|
err = templateSet.ExecuteTemplate(iw, "errors/500.html", viewArgs)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Failed to execute", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP handles all requests.
|
||||||
|
// It checks for changes to app, rebuilds if necessary, and forwards the request.
|
||||||
|
func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Don't rebuild the app for favicon requests.
|
||||||
|
if lastRequestHadError > 0 && r.URL.Path == "/favicon.ico" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush any change events and rebuild app if necessary.
|
||||||
|
// Render an error page if the rebuild / restart failed.
|
||||||
|
err := h.watcher.Notify()
|
||||||
|
if err != nil {
|
||||||
|
// In a thread safe manner update the flag so that a request for
|
||||||
|
// /favicon.ico does not trigger a rebuild
|
||||||
|
atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
|
||||||
|
h.renderError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a thread safe manner update the flag so that a request for
|
||||||
|
// /favicon.ico is allowed
|
||||||
|
atomic.CompareAndSwapInt32(&lastRequestHadError, 1, 0)
|
||||||
|
|
||||||
|
// Reverse proxy the request.
|
||||||
|
// (Need special code for websockets, courtesy of bradfitz)
|
||||||
|
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
||||||
|
h.proxyWebsocket(w, r, h.serverHost)
|
||||||
|
} else {
|
||||||
|
h.proxy.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHarness method returns a reverse proxy that forwards requests
|
||||||
|
// to the given port.
|
||||||
|
func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode string, noProxy bool) *Harness {
|
||||||
|
// Get a template loader to render errors.
|
||||||
|
// Prefer the app's views/errors directory, and fall back to the stock error pages.
|
||||||
|
//revel.MainTemplateLoader = revel.NewTemplateLoader(
|
||||||
|
// []string{filepath.Join(revel.RevelPath, "templates")})
|
||||||
|
//if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
||||||
|
// revel.RevelLog.Error("Template loader error", "error", err)
|
||||||
|
//}
|
||||||
|
|
||||||
|
addr := paths.HTTPAddr
|
||||||
|
port := paths.Config.IntDefault("harness.port", 0)
|
||||||
|
scheme := "http"
|
||||||
|
|
||||||
|
if paths.HTTPSsl {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server is running on the wildcard address, use "localhost"
|
||||||
|
if addr == "" {
|
||||||
|
utils.Logger.Warn("No http.addr specified in the app.conf listening on localhost interface only. " +
|
||||||
|
"This will not allow external access to your application")
|
||||||
|
addr = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == 0 {
|
||||||
|
port = getFreePort()
|
||||||
|
}
|
||||||
|
|
||||||
|
serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
|
||||||
|
|
||||||
|
serverHarness := &Harness{
|
||||||
|
port: port,
|
||||||
|
serverHost: serverURL.String()[len(scheme+"://"):],
|
||||||
|
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
||||||
|
mutex: &sync.Mutex{},
|
||||||
|
paths: paths,
|
||||||
|
useProxy: !noProxy,
|
||||||
|
config: c,
|
||||||
|
runMode: runMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if paths.HTTPSsl {
|
||||||
|
serverHarness.proxy.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serverHarness
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh method rebuilds the Revel application and run it on the given port.
|
||||||
|
// called by the watcher
|
||||||
|
func (h *Harness) Refresh() (err *utils.SourceError) {
|
||||||
|
t := time.Now();
|
||||||
|
fmt.Println("Changed detected, recompiling")
|
||||||
|
err = h.refresh()
|
||||||
|
if err!=nil && !h.ranOnce && h.useProxy {
|
||||||
|
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
|
||||||
|
|
||||||
|
fmt.Printf("\nError compiling code, to view error details see proxy running on http://%s\n\n",addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ranOnce = true
|
||||||
|
fmt.Printf("\nTime to recompile %s\n",time.Now().Sub(t).String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Harness) refresh() (err *utils.SourceError) {
|
||||||
|
// Allow only one thread to rebuild the process
|
||||||
|
// If multiple requests to rebuild are queued only the last one is executed on
|
||||||
|
// So before a build is started we wait for a second to determine if
|
||||||
|
// more requests for a build are triggered.
|
||||||
|
// Once no more requests are triggered the build will be processed
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
if h.app != nil {
|
||||||
|
h.app.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Logger.Info("Rebuild Called")
|
||||||
|
var newErr error
|
||||||
|
h.app, newErr = Build(h.config, h.paths)
|
||||||
|
if newErr != nil {
|
||||||
|
utils.Logger.Error("Build detected an error", "error", newErr)
|
||||||
|
if castErr, ok := newErr.(*utils.SourceError); ok {
|
||||||
|
return castErr
|
||||||
|
}
|
||||||
|
err = &utils.SourceError{
|
||||||
|
Title: "App failed to start up",
|
||||||
|
Description: err.Error(),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.useProxy {
|
||||||
|
h.app.Port = h.port
|
||||||
|
runMode := h.runMode
|
||||||
|
if !h.config.HistoricMode {
|
||||||
|
// Recalulate run mode based on the config
|
||||||
|
var paths []byte
|
||||||
|
if len(h.app.PackagePathMap)>0 {
|
||||||
|
paths, _ = json.Marshal(h.app.PackagePathMap)
|
||||||
|
}
|
||||||
|
runMode = fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, h.app.Paths.RunMode, h.config.Verbose, string(paths))
|
||||||
|
|
||||||
|
}
|
||||||
|
if err2 := h.app.Cmd(runMode).Start(h.config); err2 != nil {
|
||||||
|
utils.Logger.Error("Could not start application", "error", err2)
|
||||||
|
if err,k :=err2.(*utils.SourceError);k {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return &utils.SourceError{
|
||||||
|
Title: "App failed to start up",
|
||||||
|
Description: err2.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.app = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchDir method returns false to file matches with doNotWatch
|
||||||
|
// otheriwse true
|
||||||
|
func (h *Harness) WatchDir(info os.FileInfo) bool {
|
||||||
|
return !utils.ContainsString(doNotWatch, info.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchFile method returns true given filename HasSuffix of ".go"
|
||||||
|
// otheriwse false - implements revel.DiscerningListener
|
||||||
|
func (h *Harness) WatchFile(filename string) bool {
|
||||||
|
return strings.HasSuffix(filename, ".go")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the harness, which listens for requests and proxies them to the app
|
||||||
|
// server, which it runs and rebuilds as necessary.
|
||||||
|
func (h *Harness) Run() {
|
||||||
|
var paths []string
|
||||||
|
if h.paths.Config.BoolDefault("watch.gopath", false) {
|
||||||
|
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||||
|
paths = append(paths, gopaths...)
|
||||||
|
}
|
||||||
|
paths = append(paths, h.paths.CodePaths...)
|
||||||
|
h.watcher = watcher.NewWatcher(h.paths, false)
|
||||||
|
h.watcher.Listen(h, paths...)
|
||||||
|
go h.Refresh()
|
||||||
|
// h.watcher.Notify()
|
||||||
|
|
||||||
|
if h.useProxy {
|
||||||
|
go func() {
|
||||||
|
// Check the port to start on a random port
|
||||||
|
if h.paths.HTTPPort == 0 {
|
||||||
|
h.paths.HTTPPort = getFreePort()
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
|
||||||
|
utils.Logger.Infof("Proxy server is listening on %s", addr)
|
||||||
|
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if h.paths.HTTPSsl {
|
||||||
|
err = http.ListenAndServeTLS(
|
||||||
|
addr,
|
||||||
|
h.paths.HTTPSslCert,
|
||||||
|
h.paths.HTTPSslKey,
|
||||||
|
h)
|
||||||
|
} else {
|
||||||
|
err = http.ListenAndServe(addr, h)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Failed to start reverse proxy:", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a new channel to listen for the interrupt event
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, os.Interrupt, os.Kill)
|
||||||
|
<-ch
|
||||||
|
// Kill the app and exit
|
||||||
|
if h.app != nil {
|
||||||
|
h.app.Kill()
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an unused port
|
||||||
|
func getFreePort() (port int) {
|
||||||
|
conn, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Unable to fetch a freee port address", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
port = conn.Addr().(*net.TCPAddr).Port
|
||||||
|
err = conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Fatal("Unable to close port", "error", err)
|
||||||
|
}
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyWebsocket copies data between websocket client and server until one side
|
||||||
|
// closes the connection. (ReverseProxy doesn't work with websocket requests.)
|
||||||
|
func (h *Harness) proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
||||||
|
var (
|
||||||
|
d net.Conn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if h.paths.HTTPSsl {
|
||||||
|
// since this proxy isn't used in production,
|
||||||
|
// it's OK to set InsecureSkipVerify to true
|
||||||
|
// no need to add another configuration option.
|
||||||
|
d, err = tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true})
|
||||||
|
} else {
|
||||||
|
d, err = net.Dial("tcp", host)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error contacting backend server.", 500)
|
||||||
|
utils.Logger.Error("Error dialing websocket backend ", "host", host, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hj, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "Not a hijacker?", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nc, _, err := hj.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Hijack error", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err = nc.Close(); err != nil {
|
||||||
|
utils.Logger.Error("Connection close error", "error", err)
|
||||||
|
}
|
||||||
|
if err = d.Close(); err != nil {
|
||||||
|
utils.Logger.Error("Dial close error", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = r.Write(d)
|
||||||
|
if err != nil {
|
||||||
|
utils.Logger.Error("Error copying request to target", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errc := make(chan error, 2)
|
||||||
|
cp := func(dst io.Writer, src io.Reader) {
|
||||||
|
_, err := io.Copy(dst, src)
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
go cp(d, nc)
|
||||||
|
go cp(nc, d)
|
||||||
|
<-errc
|
||||||
|
}
|
427
app/cmd/parser2/source_info_processor.go
Normal file
427
app/cmd/parser2/source_info_processor.go
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
package parser2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"strings"
|
||||||
|
"path/filepath"
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
SourceInfoProcessor struct {
|
||||||
|
sourceProcessor *SourceProcessor
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSourceInfoProcessor(sourceProcessor *SourceProcessor) *SourceInfoProcessor {
|
||||||
|
return &SourceInfoProcessor{sourceProcessor:sourceProcessor}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *model.SourceInfo) {
|
||||||
|
sourceInfo = &model.SourceInfo{
|
||||||
|
ValidationKeys: map[string]map[int]string{},
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
isController = strings.HasSuffix(p.PkgPath, "/controllers") ||
|
||||||
|
strings.Contains(p.PkgPath, "/controllers/")
|
||||||
|
isTest = strings.HasSuffix(p.PkgPath, "/tests") ||
|
||||||
|
strings.Contains(p.PkgPath, "/tests/")
|
||||||
|
methodMap = map[string][]*model.MethodSpec{}
|
||||||
|
)
|
||||||
|
localImportMap := map[string]string{}
|
||||||
|
log := s.sourceProcessor.log.New("package", p.PkgPath)
|
||||||
|
log.Info("Processing package")
|
||||||
|
for _, tree := range p.Syntax {
|
||||||
|
for _, decl := range tree.Decls {
|
||||||
|
|
||||||
|
s.sourceProcessor.packageMap[p.PkgPath] = filepath.Dir(p.Fset.Position(decl.Pos()).Filename)
|
||||||
|
if !s.addImport(decl, p, localImportMap, log) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
spec, found := s.getStructTypeDecl(decl, p.Fset)
|
||||||
|
//log.Info("Checking file","filename", p.Fset.Position(decl.Pos()).Filename,"found",found)
|
||||||
|
if found {
|
||||||
|
if isController || isTest {
|
||||||
|
controllerSpec := s.getControllerSpec(spec, p, localImportMap)
|
||||||
|
sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not a type definition, this could be a method for a controller try to extract that
|
||||||
|
// Func declaration?
|
||||||
|
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// This could be a controller action endpoint, check and add if needed
|
||||||
|
if isController &&
|
||||||
|
funcDecl.Recv != nil && // Must have a receiver
|
||||||
|
funcDecl.Name.IsExported() && // be public
|
||||||
|
funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) == 1 {
|
||||||
|
// return one result
|
||||||
|
if m, receiver := s.getControllerFunc(funcDecl, p, localImportMap); m != nil {
|
||||||
|
methodMap[receiver] = append(methodMap[receiver], m)
|
||||||
|
log.Info("Added method map to ", "receiver", receiver, "method", m.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for validation
|
||||||
|
if lineKeyMap := s.getValidation(funcDecl, p); len(lineKeyMap) > 1 {
|
||||||
|
sourceInfo.ValidationKeys[p.PkgPath + "." + s.getFuncName(funcDecl)] = lineKeyMap
|
||||||
|
}
|
||||||
|
if funcDecl.Name.Name == "init" {
|
||||||
|
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, p.PkgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the method specs to the struct specs.
|
||||||
|
for _, spec := range sourceInfo.StructSpecs {
|
||||||
|
spec.MethodSpecs = methodMap[spec.StructName]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
||||||
|
//
|
||||||
|
// Recognize these scenarios:
|
||||||
|
// - "Y" = "Validation" and is a member of the receiver.
|
||||||
|
// (The common case for inline validation)
|
||||||
|
// - "X" is passed in to the func as a parameter.
|
||||||
|
// (For structs implementing Validated)
|
||||||
|
//
|
||||||
|
// The line number to which a validation call is attributed is that of the
|
||||||
|
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
||||||
|
// reports.
|
||||||
|
//
|
||||||
|
// The end result is that we can set the default validation key for each call to
|
||||||
|
// be the same as the local variable.
|
||||||
|
func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.Package) (map[int]string) {
|
||||||
|
var (
|
||||||
|
lineKeys = make(map[int]string)
|
||||||
|
|
||||||
|
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
||||||
|
validationParam = s.getValidationParameter(funcDecl)
|
||||||
|
)
|
||||||
|
|
||||||
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
|
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
||||||
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// e.g. c.Validation.Required or v.Required
|
||||||
|
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x := funcSelector.X.(type) {
|
||||||
|
case *ast.SelectorExpr: // e.g. c.Validation
|
||||||
|
if x.Sel.Name != "Validation" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.Ident: // e.g. v
|
||||||
|
if validationParam == nil || x.Obj != validationParam {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(callExpr.Args) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the validation expression, extract the key.
|
||||||
|
key := callExpr.Args[0]
|
||||||
|
switch expr := key.(type) {
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
// If the argument is a binary expression, take the first expression.
|
||||||
|
// (e.g. c.Validation.Required(myName != ""))
|
||||||
|
key = expr.X
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
// If the argument is a unary expression, drill in.
|
||||||
|
// (e.g. c.Validation.Required(!myBool)
|
||||||
|
key = expr.X
|
||||||
|
case *ast.BasicLit:
|
||||||
|
// If it's a literal, skip it.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
|
||||||
|
lineKeys[p.Fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
|
||||||
|
} else {
|
||||||
|
s.sourceProcessor.log.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", p.PkgPath,
|
||||||
|
"line", p.Fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return lineKeys
|
||||||
|
|
||||||
|
}
|
||||||
|
// Check to see if there is a *revel.Validation as an argument.
|
||||||
|
func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *ast.Object {
|
||||||
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
|
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if selExpr.Sel.Name == "Validation" && s.sourceProcessor.importMap[xIdent.Name] == model.RevelImportPath {
|
||||||
|
return field.Names[0].Obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packages.Package, localImportMap map[string]string) (method *model.MethodSpec, recvTypeName string) {
|
||||||
|
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if selExpr.Sel.Name != "Result" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || s.sourceProcessor.importMap[pkgIdent.Name] != model.RevelImportPath {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
method = &model.MethodSpec{
|
||||||
|
Name: funcDecl.Name.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a description of the arguments to the method.
|
||||||
|
for _, field := range funcDecl.Type.Params.List {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
var importPath string
|
||||||
|
typeExpr := model.NewTypeExprFromAst(p.Name, field.Type)
|
||||||
|
if !typeExpr.Valid {
|
||||||
|
utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, s.getFuncName(funcDecl))
|
||||||
|
return // We didn't understand one of the args. Ignore this action.
|
||||||
|
}
|
||||||
|
// Local object
|
||||||
|
if typeExpr.PkgName == p.Name {
|
||||||
|
importPath = p.PkgPath
|
||||||
|
} else if typeExpr.PkgName != "" {
|
||||||
|
var ok bool
|
||||||
|
if importPath, ok = localImportMap[typeExpr.PkgName]; !ok {
|
||||||
|
if importPath, ok = s.sourceProcessor.importMap[typeExpr.PkgName]; !ok {
|
||||||
|
utils.Logger.Error("Unable to find import", "importMap", s.sourceProcessor.importMap, "localimport", localImportMap)
|
||||||
|
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
method.Args = append(method.Args, &model.MethodArg{
|
||||||
|
Name: name.Name,
|
||||||
|
TypeExpr: typeExpr,
|
||||||
|
ImportPath: importPath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a description of the calls to Render from the method.
|
||||||
|
// Inspect every node (e.g. always return true).
|
||||||
|
method.RenderCalls = []*model.MethodCall{}
|
||||||
|
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||||
|
// Is it a function call?
|
||||||
|
callExpr, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it calling (*Controller).Render?
|
||||||
|
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type of the receiver is not easily available, so just store every
|
||||||
|
// call to any method called Render.
|
||||||
|
if selExpr.Sel.Name != "Render" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this call's args to the renderArgs.
|
||||||
|
pos := p.Fset.Position(callExpr.Lparen)
|
||||||
|
methodCall := &model.MethodCall{
|
||||||
|
Line: pos.Line,
|
||||||
|
Names: []string{},
|
||||||
|
}
|
||||||
|
for _, arg := range callExpr.Args {
|
||||||
|
argIdent, ok := arg.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
||||||
|
}
|
||||||
|
method.RenderCalls = append(method.RenderCalls, methodCall)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
var recvType = funcDecl.Recv.List[0].Type
|
||||||
|
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||||
|
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||||
|
} else {
|
||||||
|
recvTypeName = recvType.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package, localImportMap map[string]string) (controllerSpec *model.TypeInfo) {
|
||||||
|
structType := spec.Type.(*ast.StructType)
|
||||||
|
|
||||||
|
// At this point we know it's a type declaration for a struct.
|
||||||
|
// Fill in the rest of the info by diving into the fields.
|
||||||
|
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||||
|
controllerSpec = &model.TypeInfo{
|
||||||
|
StructName: spec.Name.Name,
|
||||||
|
ImportPath: p.PkgPath,
|
||||||
|
PackageName: p.Name,
|
||||||
|
}
|
||||||
|
log := s.sourceProcessor.log.New("file", p.Fset.Position(spec.Pos()).Filename, "position", p.Fset.Position(spec.Pos()).Line)
|
||||||
|
for _, field := range structType.Fields.List {
|
||||||
|
// If field.Names is set, it's not an embedded type.
|
||||||
|
if field.Names != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// A direct "sub-type" has an ast.Field as either:
|
||||||
|
// Ident { "AppController" }
|
||||||
|
// SelectorExpr { "rev", "Controller" }
|
||||||
|
// Additionally, that can be wrapped by StarExprs.
|
||||||
|
fieldType := field.Type
|
||||||
|
pkgName, typeName := func() (string, string) {
|
||||||
|
// Drill through any StarExprs.
|
||||||
|
for {
|
||||||
|
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
||||||
|
fieldType = starExpr.X
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the embedded type is in the same package, it's an Ident.
|
||||||
|
if ident, ok := fieldType.(*ast.Ident); ok {
|
||||||
|
return "", ident.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
||||||
|
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
||||||
|
return pkgIdent.Name, selectorExpr.Sel.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If a typename wasn't found, skip it.
|
||||||
|
if typeName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the import path for this type.
|
||||||
|
// If it was referenced without a package name, use the current package import path.
|
||||||
|
// Else, look up the package's import path by name.
|
||||||
|
var importPath string
|
||||||
|
if pkgName == "" {
|
||||||
|
importPath = p.PkgPath
|
||||||
|
} else {
|
||||||
|
var ok bool
|
||||||
|
if importPath, ok = localImportMap[pkgName]; !ok {
|
||||||
|
log.Debug("Debug: Unusual, failed to find package locally ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", )
|
||||||
|
if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok {
|
||||||
|
log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", )
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
|
||||||
|
ImportPath: importPath,
|
||||||
|
StructName: typeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
s.sourceProcessor.log.Info("Added controller spec", "name", controllerSpec.StructName, "package", controllerSpec.ImportPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Tok != token.TYPE {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(genDecl.Specs) == 0 {
|
||||||
|
utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
spec = genDecl.Specs[0].(*ast.TypeSpec)
|
||||||
|
_, found = spec.Type.(*ast.StructType)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string {
|
||||||
|
prefix := ""
|
||||||
|
if funcDecl.Recv != nil {
|
||||||
|
recvType := funcDecl.Recv.List[0].Type
|
||||||
|
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||||
|
prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")"
|
||||||
|
} else {
|
||||||
|
prefix = recvType.(*ast.Ident).Name
|
||||||
|
}
|
||||||
|
prefix += "."
|
||||||
|
}
|
||||||
|
return prefix + funcDecl.Name.Name
|
||||||
|
}
|
||||||
|
func (s *SourceInfoProcessor) addImport(decl ast.Decl, p *packages.Package, localImportMap map[string]string, log logger.MultiLogger) (shouldContinue bool) {
|
||||||
|
shouldContinue = true
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if genDecl.Tok == token.IMPORT {
|
||||||
|
shouldContinue = false
|
||||||
|
for _, spec := range genDecl.Specs {
|
||||||
|
importSpec := spec.(*ast.ImportSpec)
|
||||||
|
//fmt.Printf("*** import specification %#v\n", importSpec)
|
||||||
|
var pkgAlias string
|
||||||
|
if importSpec.Name != nil {
|
||||||
|
pkgAlias = importSpec.Name.Name
|
||||||
|
if pkgAlias == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
||||||
|
fullPath := quotedPath[1 : len(quotedPath) - 1] // Remove the quotes
|
||||||
|
if pkgAlias == "" {
|
||||||
|
pkgAlias = fullPath
|
||||||
|
if index := strings.LastIndex(pkgAlias, "/"); index > 0 {
|
||||||
|
pkgAlias = pkgAlias[index + 1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localImportMap[pkgAlias] = fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
310
app/cmd/parser2/source_processor.go
Normal file
310
app/cmd/parser2/source_processor.go
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
package parser2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
SourceProcessor struct {
|
||||||
|
revelContainer *model.RevelContainer
|
||||||
|
log logger.MultiLogger
|
||||||
|
packageList []*packages.Package
|
||||||
|
importMap map[string]string
|
||||||
|
packageMap map[string]string
|
||||||
|
sourceInfoProcessor *SourceInfoProcessor
|
||||||
|
sourceInfo *model.SourceInfo
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProcessSource(revelContainer *model.RevelContainer) (sourceInfo *model.SourceInfo, compileError error) {
|
||||||
|
utils.Logger.Info("ProcessSource")
|
||||||
|
processor := NewSourceProcessor(revelContainer)
|
||||||
|
compileError = processor.parse()
|
||||||
|
sourceInfo = processor.sourceInfo
|
||||||
|
if compileError == nil {
|
||||||
|
processor.log.Infof("From parsers : Structures:%d InitImports:%d ValidationKeys:%d %v", len(sourceInfo.StructSpecs), len(sourceInfo.InitImportPaths), len(sourceInfo.ValidationKeys), sourceInfo.PackageMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor {
|
||||||
|
s := &SourceProcessor{revelContainer:revelContainer, log:utils.Logger.New("parser", "SourceProcessor")}
|
||||||
|
s.sourceInfoProcessor = NewSourceInfoProcessor(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceProcessor) parse() (compileError error) {
|
||||||
|
print("Parsing packages, (may require download if not cached)...")
|
||||||
|
if compileError = s.addPackages(); compileError != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println(" Completed")
|
||||||
|
if compileError = s.addImportMap(); compileError != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if compileError = s.addSourceInfo(); compileError != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.sourceInfo.PackageMap = map[string]string{}
|
||||||
|
getImportFromMap := func(packagePath string) string {
|
||||||
|
for path := range s.packageMap {
|
||||||
|
if strings.Index(path, packagePath) == 0 {
|
||||||
|
fullPath := s.packageMap[path]
|
||||||
|
return fullPath[:(len(fullPath) - len(path) + len(packagePath))]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s.sourceInfo.PackageMap[model.RevelImportPath] = getImportFromMap(model.RevelImportPath)
|
||||||
|
s.sourceInfo.PackageMap[s.revelContainer.ImportPath] = getImportFromMap(s.revelContainer.ImportPath)
|
||||||
|
for _, module := range s.revelContainer.ModulePathMap {
|
||||||
|
s.sourceInfo.PackageMap[module.ImportPath] = getImportFromMap(module.ImportPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这两个方法来自util
|
||||||
|
|
||||||
|
// Shortcut to fsWalk
|
||||||
|
func (s *SourceProcessor) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return s.fsWalk(root, root, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk the path tree using the function
|
||||||
|
// Every file found will call the function
|
||||||
|
func (s *SourceProcessor) fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
|
||||||
|
fsWalkFunc := func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
name, err = filepath.Rel(fname, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(linkName, name)
|
||||||
|
|
||||||
|
// 改了这里
|
||||||
|
if strings.Contains(path, "/leanote/public") ||
|
||||||
|
strings.Contains(path, "/leanote/files") ||
|
||||||
|
strings.Contains(path, "/leanote/doc") ||
|
||||||
|
strings.Contains(path, "/leanote/logs") ||
|
||||||
|
strings.Contains(path, "/leanote/build") ||
|
||||||
|
strings.Contains(path, "/leanote/target") {
|
||||||
|
s.log.Warn("public 或 files 不要处理", "path", path)
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && info.Mode() & os.ModeSymlink == os.ModeSymlink {
|
||||||
|
var symlinkPath string
|
||||||
|
symlinkPath, err = filepath.EvalSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392
|
||||||
|
info, err = os.Lstat(symlinkPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
return s.fsWalk(symlinkPath, path, walkFn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
err := filepath.Walk(fname, fsWalkFunc)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using the packages.Load function load all the packages and type specifications (forces compile).
|
||||||
|
// this sets the SourceProcessor.packageList []*packages.Package
|
||||||
|
func (s *SourceProcessor) addPackages() (err error) {
|
||||||
|
allPackages := []string{model.RevelImportPath + "/..."}
|
||||||
|
for _, module := range s.revelContainer.ModulePathMap {
|
||||||
|
allPackages = append(allPackages, module.ImportPath + "/...") // +"/app/controllers/...")
|
||||||
|
}
|
||||||
|
s.log.Info("Reading packages", "packageList", allPackages)
|
||||||
|
//allPackages = []string{s.revelContainer.ImportPath + "/..."} //+"/app/controllers/..."}
|
||||||
|
|
||||||
|
config := &packages.Config{
|
||||||
|
// ode: packages.NeedSyntax | packages.NeedCompiledGoFiles,
|
||||||
|
Mode:
|
||||||
|
packages.NeedTypes | // For compile error
|
||||||
|
packages.NeedDeps | // To load dependent files
|
||||||
|
packages.NeedName | // Loads the full package name
|
||||||
|
packages.NeedSyntax, // To load ast tree (for end points)
|
||||||
|
//Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
|
||||||
|
// packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile |
|
||||||
|
// packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo |
|
||||||
|
// packages.NeedTypesSizes,
|
||||||
|
|
||||||
|
//Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles |
|
||||||
|
// packages.NeedCompiledGoFiles | packages.NeedTypesSizes |
|
||||||
|
// packages.NeedSyntax | packages.NeedCompiledGoFiles ,
|
||||||
|
//Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles |
|
||||||
|
// packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // |
|
||||||
|
// packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo,
|
||||||
|
//packages.LoadSyntax | packages.NeedDeps,
|
||||||
|
Dir:s.revelContainer.AppPath,
|
||||||
|
}
|
||||||
|
s.packageList, err = packages.Load(config, allPackages...)
|
||||||
|
s.log.Info("Loaded modules ", "len results", len(s.packageList), "error", err)
|
||||||
|
|
||||||
|
// Now process the files in the aap source folder s.revelContainer.ImportPath + "/...",
|
||||||
|
err = s.Walk(s.revelContainer.BasePath, s.processPath)
|
||||||
|
s.log.Info("Loaded apps and modules ", "len results", len(s.packageList), "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This callback is used to build the packages for the "app" package. This allows us to
|
||||||
|
// parse the source files without doing a full compile on them
|
||||||
|
// This callback only processes folders, so any files passed to this will return a nil
|
||||||
|
func (s *SourceProcessor) processPath(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("Error scanning app source:", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore files and folders not marked tmp (since those are generated)
|
||||||
|
if !info.IsDir() || info.Name() == "tmp" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real work for processing the folder
|
||||||
|
pkgImportPath := s.revelContainer.ImportPath
|
||||||
|
appPath := s.revelContainer.BasePath
|
||||||
|
if appPath != path {
|
||||||
|
pkgImportPath = s.revelContainer.ImportPath + "/" + filepath.ToSlash(path[len(appPath) + 1:])
|
||||||
|
}
|
||||||
|
s.log.Info("Processing source package folder", "package", pkgImportPath, "path", path)
|
||||||
|
|
||||||
|
// Parse files within the path.
|
||||||
|
var pkgMap map[string]*ast.Package
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
pkgMap, err = parser.ParseDir(
|
||||||
|
fset,
|
||||||
|
path,
|
||||||
|
func(f os.FileInfo) bool {
|
||||||
|
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
||||||
|
},
|
||||||
|
0)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errList, ok := err.(scanner.ErrorList); ok {
|
||||||
|
var pos = errList[0].Pos
|
||||||
|
newError := &utils.SourceError{
|
||||||
|
SourceType: ".go source",
|
||||||
|
Title: "Go Compilation Error",
|
||||||
|
Path: pos.Filename,
|
||||||
|
Description: errList[0].Msg,
|
||||||
|
Line: pos.Line,
|
||||||
|
Column: pos.Column,
|
||||||
|
SourceLines: utils.MustReadLines(pos.Filename),
|
||||||
|
}
|
||||||
|
|
||||||
|
errorLink := s.revelContainer.Config.StringDefault("error.link", "")
|
||||||
|
if errorLink != "" {
|
||||||
|
newError.SetLink(errorLink)
|
||||||
|
}
|
||||||
|
return newError
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is exception, err already checked above. Here just a print
|
||||||
|
ast.Print(nil, err)
|
||||||
|
s.log.Fatal("Failed to parse dir", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip "main" packages.
|
||||||
|
delete(pkgMap, "main")
|
||||||
|
|
||||||
|
// Ignore packages that end with _test
|
||||||
|
// These cannot be included in source code that is not generated specifically as a test
|
||||||
|
for i := range pkgMap {
|
||||||
|
if len(i) > 6 {
|
||||||
|
if string(i[len(i) - 5:]) == "_test" {
|
||||||
|
delete(pkgMap, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no code in this directory, skip it.
|
||||||
|
if len(pkgMap) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There should be only one package in this directory.
|
||||||
|
if len(pkgMap) > 1 {
|
||||||
|
for i := range pkgMap {
|
||||||
|
println("Found duplicate packages in single directory ", i)
|
||||||
|
}
|
||||||
|
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point there is only one package in the pkgs map,
|
||||||
|
p := &packages.Package{}
|
||||||
|
p.PkgPath = pkgImportPath
|
||||||
|
p.Fset = fset
|
||||||
|
for _, pkg := range pkgMap {
|
||||||
|
p.Name = pkg.Name
|
||||||
|
s.log.Info("Found package", "pkg.Name", pkg.Name, "p.Name", p.PkgPath)
|
||||||
|
for filename, astFile := range pkg.Files {
|
||||||
|
p.Syntax = append(p.Syntax, astFile)
|
||||||
|
p.GoFiles = append(p.GoFiles, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.packageList = append(s.packageList, p)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is used to populate a map so that we can lookup controller embedded types in order to determine
|
||||||
|
// if a Struct inherits from from revel.Controller
|
||||||
|
func (s *SourceProcessor) addImportMap() (err error) {
|
||||||
|
s.importMap = map[string]string{}
|
||||||
|
s.packageMap = map[string]string{}
|
||||||
|
for _, p := range s.packageList {
|
||||||
|
|
||||||
|
if len(p.Errors) > 0 {
|
||||||
|
// Generate a compile error
|
||||||
|
for _, e := range p.Errors {
|
||||||
|
s.log.Info("While reading packages encountered import error ignoring ", "PkgPath", p.PkgPath, "error", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tree := range p.Syntax {
|
||||||
|
s.importMap[tree.Name.Name] = p.PkgPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SourceProcessor) addSourceInfo() (err error) {
|
||||||
|
for _, p := range s.packageList {
|
||||||
|
if sourceInfo := s.sourceInfoProcessor.processPackage(p); sourceInfo != nil {
|
||||||
|
if s.sourceInfo != nil {
|
||||||
|
s.sourceInfo.Merge(sourceInfo)
|
||||||
|
} else {
|
||||||
|
s.sourceInfo = sourceInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
151
app/cmd/revel.go
Normal file
151
app/cmd/revel.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||||
|
// Revel Framework source code and usage is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// The command line tool for running Revel apps.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jessevdk/go-flags"
|
||||||
|
|
||||||
|
"github.com/agtorre/gocolorize"
|
||||||
|
"github.com/revel/cmd/logger"
|
||||||
|
"github.com/revel/cmd/model"
|
||||||
|
"github.com/revel/cmd/utils"
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RevelCmdImportPath Revel framework cmd tool import path
|
||||||
|
RevelCmdImportPath = "github.com/revel/cmd"
|
||||||
|
|
||||||
|
// RevelCmdImportPath Revel framework cmd tool import path
|
||||||
|
RevelSkeletonsImportPath = "github.com/revel/skeletons"
|
||||||
|
|
||||||
|
// DefaultRunMode for revel's application
|
||||||
|
DefaultRunMode = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command structure cribbed from the genius organization of the "go" command.
|
||||||
|
type Command struct {
|
||||||
|
UpdateConfig func(c *model.CommandConfig, args []string) bool
|
||||||
|
RunWith func(c *model.CommandConfig) error
|
||||||
|
UsageLine, Short, Long string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns command name from usage line
|
||||||
|
func (cmd *Command) Name() string {
|
||||||
|
name := cmd.UsageLine
|
||||||
|
i := strings.Index(name, " ")
|
||||||
|
if i >= 0 {
|
||||||
|
name = name[:i]
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// The commands
|
||||||
|
var Commands = []*Command{
|
||||||
|
nil, // Safety net, prevent missing index from running
|
||||||
|
|
||||||
|
// 只改了这个
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
cmdBuild,
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
gocolorize.SetPlain(true)
|
||||||
|
}
|
||||||
|
c := &model.CommandConfig{}
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
|
utils.InitLogger(wd, logger.LvlError)
|
||||||
|
parser := flags.NewParser(c, flags.HelpFlag | flags.PassDoubleDash)
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
parser.WriteHelp(os.Stdout)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ParseArgs(c, parser, os.Args[1:]); err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, err.Error() + "\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch based on the verbose flag
|
||||||
|
if len(c.Verbose) > 1 {
|
||||||
|
utils.InitLogger(wd, logger.LvlDebug)
|
||||||
|
} else if len(c.Verbose) > 0 {
|
||||||
|
utils.InitLogger(wd, logger.LvlInfo)
|
||||||
|
} else {
|
||||||
|
utils.InitLogger(wd, logger.LvlWarn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup package resolver
|
||||||
|
c.InitPackageResolver()
|
||||||
|
|
||||||
|
if err := c.UpdateImportPath(); err != nil {
|
||||||
|
utils.Logger.Error(err.Error())
|
||||||
|
parser.WriteHelp(os.Stdout)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
command := Commands[c.Index]
|
||||||
|
println("Revel executing:", command.Short)
|
||||||
|
|
||||||
|
if err := command.RunWith(c); err != nil {
|
||||||
|
utils.Logger.Error("Unable to execute", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the arguments passed into the model.CommandConfig
|
||||||
|
func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err error) {
|
||||||
|
var extraArgs []string
|
||||||
|
if ini := flag.String("ini", "none", ""); *ini != "none" {
|
||||||
|
if err = flags.NewIniParser(parser).ParseFile(*ini); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if extraArgs, err = parser.ParseArgs(args); err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
switch parser.Active.Name {
|
||||||
|
case "new":
|
||||||
|
c.Index = model.NEW
|
||||||
|
case "run":
|
||||||
|
c.Index = model.RUN
|
||||||
|
case "build":
|
||||||
|
c.Index = model.BUILD
|
||||||
|
case "package":
|
||||||
|
c.Index = model.PACKAGE
|
||||||
|
case "clean":
|
||||||
|
c.Index = model.CLEAN
|
||||||
|
case "test":
|
||||||
|
c.Index = model.TEST
|
||||||
|
case "version":
|
||||||
|
c.Index = model.VERSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Commands[c.Index].UpdateConfig(c, extraArgs) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
parser.WriteHelp(buffer)
|
||||||
|
err = fmt.Errorf("Invalid command line arguements %v\n%s", extraArgs, buffer.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# Comma delimited list of folders that are included with the package, or build commands
|
# Comma delimited list of folders that are included with the package, or build commands
|
||||||
# If you want to not include folders within these ones prefix the folder with a . to make it hidden
|
# If you want to not include folders within these ones prefix the folder with a . to make it hidden
|
||||||
package.folders = conf, public, messages, app/views
|
package.folders = conf, public, messages, mongodb_backup, app/views
|
||||||
|
|
||||||
|
|
||||||
#------------------------
|
#------------------------
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# Comma delimited list of folders that are included with the package, or build commands
|
# Comma delimited list of folders that are included with the package, or build commands
|
||||||
# If you want to not include folders within these ones prefix the folder with a . to make it hidden
|
# If you want to not include folders within these ones prefix the folder with a . to make it hidden
|
||||||
package.folders = conf, public, messages, app/views
|
package.folders = conf, public, messages, mongodb_backup, app/views
|
||||||
|
|
||||||
|
|
||||||
#------------------------
|
#------------------------
|
||||||
|
6
go.mod
6
go.mod
@ -4,13 +4,16 @@ go 1.15
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.6.1
|
github.com/PuerkitoBio/goquery v1.6.1
|
||||||
|
github.com/agtorre/gocolorize v1.0.0
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/garyburd/redigo v1.6.2 // indirect
|
github.com/garyburd/redigo v1.6.2 // indirect
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect
|
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect
|
||||||
|
github.com/jessevdk/go-flags v1.4.0
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/revel/cmd v1.0.3
|
||||||
github.com/revel/config v1.0.0 // indirect
|
github.com/revel/config v1.0.0 // indirect
|
||||||
github.com/revel/log15 v2.11.20+incompatible // indirect
|
github.com/revel/log15 v2.11.20+incompatible // indirect
|
||||||
github.com/revel/modules v1.0.0
|
github.com/revel/modules v1.0.0
|
||||||
@ -21,7 +24,8 @@ require (
|
|||||||
github.com/xeonx/timeago v1.0.0-rc4 // indirect
|
github.com/xeonx/timeago v1.0.0-rc4 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
|
||||||
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056 // indirect
|
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056 // indirect
|
||||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce
|
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0 // indirect
|
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0 // indirect
|
||||||
)
|
)
|
||||||
|
23
go.sum
23
go.sum
@ -3,16 +3,20 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go
|
|||||||
github.com/Masterminds/squirrel v1.3.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA=
|
github.com/Masterminds/squirrel v1.3.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA=
|
||||||
github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk=
|
github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk=
|
||||||
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
|
github.com/agtorre/gocolorize v1.0.0 h1:TvGQd+fAqWQlDjQxSKe//Y6RaxK+RHpEU9X/zPmHW50=
|
||||||
|
github.com/agtorre/gocolorize v1.0.0/go.mod h1:cH6imfTkHVBRJhSOeSeEZhB4zqEYSq0sXuIyehgZMIY=
|
||||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
|
github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
|
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
|
github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
|
||||||
@ -28,6 +32,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
|||||||
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
|
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
|
||||||
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI=
|
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI=
|
||||||
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
|
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
|
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
@ -56,14 +62,19 @@ github.com/newrelic/go-agent v3.4.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDk
|
|||||||
github.com/patrickmn/go-cache v1.0.0 h1:3gD5McaYs9CxjyK5AXGcq8gdeCARtd/9gJDUvVeaZ0Y=
|
github.com/patrickmn/go-cache v1.0.0 h1:3gD5McaYs9CxjyK5AXGcq8gdeCARtd/9gJDUvVeaZ0Y=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/poy/onpar v0.0.0-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
|
github.com/poy/onpar v0.0.0-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
|
||||||
|
github.com/revel/cmd v1.0.3 h1:76YrotXjCcHI+oY9UtV7VP2SMsPfvDyet+AstcLYXoY=
|
||||||
|
github.com/revel/cmd v1.0.3/go.mod h1:44aFxD+KIr2w22yamS179ZrdyupHEXyko+2IsPK7+Wk=
|
||||||
github.com/revel/config v0.21.0/go.mod h1:GT4a9px5kDGRqLizcw/md0QFErrhen76toz4qS3oIoI=
|
github.com/revel/config v0.21.0/go.mod h1:GT4a9px5kDGRqLizcw/md0QFErrhen76toz4qS3oIoI=
|
||||||
github.com/revel/config v1.0.0 h1:UAzLPQ+x9nJeP6a+H93G+AKEosg3OO2oVLBXK9oSN2U=
|
github.com/revel/config v1.0.0 h1:UAzLPQ+x9nJeP6a+H93G+AKEosg3OO2oVLBXK9oSN2U=
|
||||||
github.com/revel/config v1.0.0/go.mod h1:GT4a9px5kDGRqLizcw/md0QFErrhen76toz4qS3oIoI=
|
github.com/revel/config v1.0.0/go.mod h1:GT4a9px5kDGRqLizcw/md0QFErrhen76toz4qS3oIoI=
|
||||||
github.com/revel/cron v0.21.0/go.mod h1:WrSp8p1H1IfOGumbbDGrGf8dZLjNSnGSnwxTj3nG80I=
|
github.com/revel/cron v0.21.0/go.mod h1:WrSp8p1H1IfOGumbbDGrGf8dZLjNSnGSnwxTj3nG80I=
|
||||||
github.com/revel/log15 v2.11.20+incompatible h1:JkA4tbwIo/UGEMumY50zndKq816RQW3LQ0wIpRc+32U=
|
github.com/revel/log15 v2.11.20+incompatible h1:JkA4tbwIo/UGEMumY50zndKq816RQW3LQ0wIpRc+32U=
|
||||||
github.com/revel/log15 v2.11.20+incompatible/go.mod h1:l0WmLRs+IM1hBl4noJiBc2tZQiOgZyXzS1mdmFt+5Gc=
|
github.com/revel/log15 v2.11.20+incompatible/go.mod h1:l0WmLRs+IM1hBl4noJiBc2tZQiOgZyXzS1mdmFt+5Gc=
|
||||||
|
github.com/revel/modules v0.21.0/go.mod h1:UBlNmO9VGZo4j6Ptn2uC/26Iclefuic+V40jYRPBxQE=
|
||||||
github.com/revel/modules v1.0.0 h1:JrqUDCU7y8Qfxrk+XcvhTUq8fv0hfzTCHNFSWYpsFBg=
|
github.com/revel/modules v1.0.0 h1:JrqUDCU7y8Qfxrk+XcvhTUq8fv0hfzTCHNFSWYpsFBg=
|
||||||
github.com/revel/modules v1.0.0/go.mod h1:wJwbm8ccPzf+a5LBv49x/6a0TdIqh/XVad8u6w+RW8E=
|
github.com/revel/modules v1.0.0/go.mod h1:wJwbm8ccPzf+a5LBv49x/6a0TdIqh/XVad8u6w+RW8E=
|
||||||
github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9 h1:/d6kfjzjyx19ieWqMOXHSTLFuRxLOH15ZubtcAXExKw=
|
github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9 h1:/d6kfjzjyx19ieWqMOXHSTLFuRxLOH15ZubtcAXExKw=
|
||||||
@ -73,7 +84,9 @@ github.com/revel/revel v1.0.0 h1:BsPFnKuuzXEkPtrjdjZHiDcvDmbBiBQvh7Z5c6kLb/Y=
|
|||||||
github.com/revel/revel v1.0.0/go.mod h1:VZWJnHjpDEtuGUuZJ2NO42XryitrtwsdVaJxfDeo5yc=
|
github.com/revel/revel v1.0.0/go.mod h1:VZWJnHjpDEtuGUuZJ2NO42XryitrtwsdVaJxfDeo5yc=
|
||||||
github.com/robfig/config v0.0.0-20141207224736-0f78529c8c7e h1:3/9k/etUfgykjM3Rx8X0echJzo7gNNeND/ubPkqYw1k=
|
github.com/robfig/config v0.0.0-20141207224736-0f78529c8c7e h1:3/9k/etUfgykjM3Rx8X0echJzo7gNNeND/ubPkqYw1k=
|
||||||
github.com/robfig/config v0.0.0-20141207224736-0f78529c8c7e/go.mod h1:Zerq1qYbCKtIIU9QgPydffGlpYfZ8KI/si49wuTLY/Q=
|
github.com/robfig/config v0.0.0-20141207224736-0f78529c8c7e/go.mod h1:Zerq1qYbCKtIIU9QgPydffGlpYfZ8KI/si49wuTLY/Q=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
|
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
|
||||||
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
|
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
|
||||||
github.com/tylerb/gls v0.0.0-20150407001822-e606233f194d/go.mod h1:0MwyId/pXK5wkYYEXe7NnVknX+aNBuF73fLV3U0reU8=
|
github.com/tylerb/gls v0.0.0-20150407001822-e606233f194d/go.mod h1:0MwyId/pXK5wkYYEXe7NnVknX+aNBuF73fLV3U0reU8=
|
||||||
@ -87,17 +100,21 @@ github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+
|
|||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
||||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056 h1:sANdAef76Ioam9aQUUdcAqricwY/WUaMc4+7LY4eGg8=
|
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056 h1:sANdAef76Ioam9aQUUdcAqricwY/WUaMc4+7LY4eGg8=
|
||||||
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -106,6 +123,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
|
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
|
||||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -114,11 +132,16 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85 h1:XNHaQ2CZDl/SjEZlUXGh7+OQvfLuFgmk3oNWkCFfERE=
|
||||||
|
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
|
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
|
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0 h1:lMH45EKqD8Nf6LwoF+43YOKjOAEEHQRVgDyG8RCV4MU=
|
gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0 h1:lMH45EKqD8Nf6LwoF+43YOKjOAEEHQRVgDyG8RCV4MU=
|
||||||
|
@ -11,7 +11,7 @@ My Group=Mis grupos
|
|||||||
# post abstract
|
# post abstract
|
||||||
|
|
||||||
Abstract=Resumen
|
Abstract=Resumen
|
||||||
Description=Desripción
|
Description=Descripción
|
||||||
Once the abstract has been updated, it will not set the abstract automatically other than you cancel it.=Una vez actualizado el resumen, no se establecerá el resumen automáticamente salvo que lo cancele.
|
Once the abstract has been updated, it will not set the abstract automatically other than you cancel it.=Una vez actualizado el resumen, no se establecerá el resumen automáticamente salvo que lo cancele.
|
||||||
Main Image=Imagen principal
|
Main Image=Imagen principal
|
||||||
Get next image as main image from content=Obtener la siguiente imagen como imagen principal del contenido
|
Get next image as main image from content=Obtener la siguiente imagen como imagen principal del contenido
|
||||||
@ -26,13 +26,13 @@ normalAccountType=Normal
|
|||||||
imageSize=Capacidad de imágenes
|
imageSize=Capacidad de imágenes
|
||||||
attachSize=Capacidad de adjuntos
|
attachSize=Capacidad de adjuntos
|
||||||
totalTraffic=Tráfico Web
|
totalTraffic=Tráfico Web
|
||||||
upgrade=Upgrade Mis cuentas
|
upgrade=Actualizar Mis cuentas
|
||||||
leanoteEvents=Noticias Leanote
|
leanoteEvents=Noticias Leanote
|
||||||
addLeanoteAccount=New Leanote Account
|
addLeanoteAccount=Nueva cuenta Leanote
|
||||||
defaultComment=Comentarios del sistema leanote por defecto
|
defaultComment=Comentarios del sistema leanote por defecto
|
||||||
upgradeAccountTips=Quiero utilizar un dominio personalizado para mi blog, <a class="btn btn-default" href="/service">Actualizar mi cuenta</a>
|
upgradeAccountTips=Quiero utilizar un dominio personalizado para mi blog, <a class="btn btn-default" href="/service">Actualizar mi cuenta</a>
|
||||||
cateIsPublicNotebook=Categoría son los cuadernos publicados
|
cateIsPublicNotebook=Categoría son los cuadernos publicados
|
||||||
dragAndSort=Arrástre para ordenar
|
dragAndSort=Arrastre para ordenar
|
||||||
permanentLink=Enlace permanente
|
permanentLink=Enlace permanente
|
||||||
cate=Categoría
|
cate=Categoría
|
||||||
noCates=Sin categorías
|
noCates=Sin categorías
|
||||||
@ -41,7 +41,7 @@ singleTips=Puede añadir muchas páginas individuales
|
|||||||
addSingle=Nueva página simple
|
addSingle=Nueva página simple
|
||||||
updateSingle=Actualizar página simple
|
updateSingle=Actualizar página simple
|
||||||
inputSingleTitle=Es requerido un título de página simple
|
inputSingleTitle=Es requerido un título de página simple
|
||||||
saveSort=Guardar secuencua
|
saveSort=Guardar secuencia
|
||||||
pagingAndSort=Configuración de paginación
|
pagingAndSort=Configuración de paginación
|
||||||
perPageSize=Por tamaño de página
|
perPageSize=Por tamaño de página
|
||||||
sortField=Campo ordenado
|
sortField=Campo ordenado
|
||||||
@ -71,14 +71,14 @@ myOtherThemes=Mis otros temas
|
|||||||
leanoteThemeMarket=Tienda de temas Leanote
|
leanoteThemeMarket=Tienda de temas Leanote
|
||||||
updateTheme=Actualizar tema
|
updateTheme=Actualizar tema
|
||||||
tplStyleScript=plantilla, estilo, script
|
tplStyleScript=plantilla, estilo, script
|
||||||
newFile=New file
|
newFile=Nuevo archivo
|
||||||
image=Image
|
image=Imagen
|
||||||
currentFile=Current file
|
currentFile=Archivo actual
|
||||||
tpl=Template
|
tpl=Plantilla
|
||||||
style=Style
|
style=Estilo
|
||||||
script=Script
|
script=Script
|
||||||
header=Encabezado
|
header=Encabezado
|
||||||
footer=Píe de página
|
footer=Pie de página
|
||||||
index=Inicio
|
index=Inicio
|
||||||
cate=Categoría
|
cate=Categoría
|
||||||
search=Buscar
|
search=Buscar
|
||||||
@ -104,7 +104,7 @@ Blog Base Info=Base Info Blog
|
|||||||
Comment=Comentario
|
Comment=Comentario
|
||||||
Posts=Posts
|
Posts=Posts
|
||||||
Domain=Dominio
|
Domain=Dominio
|
||||||
Paging=Paging
|
Paging=Paginado
|
||||||
Category=Categoría
|
Category=Categoría
|
||||||
Add Single=Añadir simple
|
Add Single=Añadir simple
|
||||||
Update Single=Actualizar simple
|
Update Single=Actualizar simple
|
||||||
@ -115,7 +115,7 @@ Upate Theme=Actualizar tema
|
|||||||
group=Grupo
|
group=Grupo
|
||||||
newGroup=Nuevo grupo
|
newGroup=Nuevo grupo
|
||||||
deleteGroup=Eliminar grupo
|
deleteGroup=Eliminar grupo
|
||||||
addMemberTips=Input username or email to add member
|
addMemberTips=Ingresar nombre de usuario o correo electrónico para agregar un miembro
|
||||||
deleteMember=Eliminar miembro
|
deleteMember=Eliminar miembro
|
||||||
forbiddenNotMyGroup=No tienes autorización para acceder, porque no eres el propietario del grupo
|
forbiddenNotMyGroup=No tienes autorización para acceder, porque no eres el propietario del grupo
|
||||||
userExistsInGroup=El miembro ya existe en este grupo
|
userExistsInGroup=El miembro ya existe en este grupo
|
||||||
@ -124,7 +124,7 @@ notMyGroup=El miembro ya existe en este grupo
|
|||||||
|
|
||||||
# view js
|
# view js
|
||||||
Are you sure ?=Está seguro?
|
Are you sure ?=Está seguro?
|
||||||
Are you sure to install it ?=Esta seguro de instalar?
|
Are you sure to install it ?=Está seguro de instalar?
|
||||||
Are you sure to delete=está seguro para eliminar?
|
Are you sure to delete=está seguro para eliminar?
|
||||||
Success=Exítoso
|
Success=Exítoso
|
||||||
Error=Error
|
Error=Error
|
||||||
|
@ -6,7 +6,7 @@ Paste=Pegar
|
|||||||
Close=Cerrar
|
Close=Cerrar
|
||||||
Font Family=Familia de fuente
|
Font Family=Familia de fuente
|
||||||
Pre=Pre
|
Pre=Pre
|
||||||
Align right=Aliear a la derecha
|
Align right=Alinear a la derecha
|
||||||
New document=Nuevo documento
|
New document=Nuevo documento
|
||||||
Blockquote=Cita
|
Blockquote=Cita
|
||||||
Numbered list=Lista numerada
|
Numbered list=Lista numerada
|
||||||
@ -23,7 +23,7 @@ Header 1=Encabezado 1
|
|||||||
Superscript=Superíndice
|
Superscript=Superíndice
|
||||||
Clear formatting=Limpiar formato
|
Clear formatting=Limpiar formato
|
||||||
Font Sizes=Tamaños de fuentes
|
Font Sizes=Tamaños de fuentes
|
||||||
Subscript=Subindice
|
Subscript=Subíndice
|
||||||
Header 6=Encabezado 6
|
Header 6=Encabezado 6
|
||||||
Redo=Rehacer
|
Redo=Rehacer
|
||||||
Paragraph=Párrafo
|
Paragraph=Párrafo
|
||||||
@ -46,7 +46,7 @@ Visual aids=Ayuda visuales
|
|||||||
Lower Greek=Letra griega
|
Lower Greek=Letra griega
|
||||||
Square=Cuadrado
|
Square=Cuadrado
|
||||||
Default=Predeterminado
|
Default=Predeterminado
|
||||||
Lower Alpha=Alfa miníscula
|
Lower Alpha=Alfa minúscula
|
||||||
Circle=Círculo
|
Circle=Círculo
|
||||||
Disc=Disc
|
Disc=Disc
|
||||||
Upper Alpha=Alfa mayúscula
|
Upper Alpha=Alfa mayúscula
|
||||||
@ -54,7 +54,7 @@ Upper Roman=Romana mayúscula
|
|||||||
Lower Roman=Romanana minúscula
|
Lower Roman=Romanana minúscula
|
||||||
Name=Nombre
|
Name=Nombre
|
||||||
Anchor=Marcador
|
Anchor=Marcador
|
||||||
You have unsaved changes are you sure you want to navigate away?=¿Tiene cambios sin guardar, stá seguro que desea salir?
|
You have unsaved changes are you sure you want to navigate away?=¿Tiene cambios sin guardar, está seguro que desea salir?
|
||||||
Restaurar último borrador = Restaurar último borrador
|
Restaurar último borrador = Restaurar último borrador
|
||||||
Special character=Carácter especial
|
Special character=Carácter especial
|
||||||
Source code=Código fuente
|
Source code=Código fuente
|
||||||
@ -64,7 +64,7 @@ Emoticons=Emoticones
|
|||||||
Robots=Robots
|
Robots=Robots
|
||||||
Document properties=Propiedades del documento
|
Document properties=Propiedades del documento
|
||||||
Title=Título
|
Title=Título
|
||||||
Keywords=Palabras clase
|
Keywords=Palabras clave
|
||||||
Encoding=Codificación
|
Encoding=Codificación
|
||||||
Description=Descripción
|
Description=Descripción
|
||||||
Author=Autor
|
Author=Autor
|
||||||
@ -98,7 +98,7 @@ The URL you entered seems to be an email address. Do you want to add the require
|
|||||||
Insert/edit link=Insertar/editar enlace Web
|
Insert/edit link=Insertar/editar enlace Web
|
||||||
Insert/edit video=Insertar/editar video
|
Insert/edit video=Insertar/editar video
|
||||||
Poster=Poster
|
Poster=Poster
|
||||||
Alternative source=Codigo alternativo
|
Alternative source=Código alternativo
|
||||||
Paste your embed code below:=Pegue su código embebido aquí:
|
Paste your embed code below:=Pegue su código embebido aquí:
|
||||||
Insert video=Insertar video
|
Insert video=Insertar video
|
||||||
Embed=Embeber
|
Embed=Embeber
|
||||||
@ -111,7 +111,7 @@ Save=Guardar
|
|||||||
Could not find the specified string.=No puede encontrar la cadena especificada.
|
Could not find the specified string.=No puede encontrar la cadena especificada.
|
||||||
Replace=Reemplace
|
Replace=Reemplace
|
||||||
Next=Siguiente
|
Next=Siguiente
|
||||||
Whole words=Palbras completas
|
Whole words=Palabras completas
|
||||||
Find and replace=Encontrar y reemplazar
|
Find and replace=Encontrar y reemplazar
|
||||||
Replace with=Reemplazar con
|
Replace with=Reemplazar con
|
||||||
Find=Encontrar
|
Find=Encontrar
|
||||||
@ -157,9 +157,9 @@ Cols=Columnas
|
|||||||
Insert row after=Insertar filas después
|
Insert row after=Insertar filas después
|
||||||
Width=Ancho
|
Width=Ancho
|
||||||
Cell properties=Propiedades de celda
|
Cell properties=Propiedades de celda
|
||||||
Left=Inzquierda
|
Left=Izquierda
|
||||||
Cut row=Cortar fila
|
Cut row=Cortar fila
|
||||||
Delete column=Elimar columna
|
Delete column=Eliminar columna
|
||||||
Center=Centrar
|
Center=Centrar
|
||||||
Merge cells=Unir celdas
|
Merge cells=Unir celdas
|
||||||
Insert template=Insertar plantilla
|
Insert template=Insertar plantilla
|
||||||
@ -167,7 +167,7 @@ Templates=Plantillas
|
|||||||
Background color=Color de fondo
|
Background color=Color de fondo
|
||||||
Text color=Color de texto
|
Text color=Color de texto
|
||||||
Show blocks=Mostrar cita
|
Show blocks=Mostrar cita
|
||||||
Show invisible characters=Mostrar carécteres ocultos
|
Show invisible characters=Mostrar caracteres ocultos
|
||||||
Words: {0}=Palabras: {0}
|
Words: {0}=Palabras: {0}
|
||||||
Insert=Insertar
|
Insert=Insertar
|
||||||
File=Archivo
|
File=Archivo
|
||||||
|
@ -49,6 +49,7 @@ define('history', [], function() {
|
|||||||
if (this.note.IsMarkdown) {
|
if (this.note.IsMarkdown) {
|
||||||
wrap = '<pre>';
|
wrap = '<pre>';
|
||||||
wrapEnd = '</pre>';
|
wrapEnd = '</pre>';
|
||||||
|
content = trimTitle(content) // for xss
|
||||||
}
|
}
|
||||||
$historyContent.html(wrap + content + wrapEnd);
|
$historyContent.html(wrap + content + wrapEnd);
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ tinymce.PluginManager.add('leanote_nav', function(editor) {
|
|||||||
var text = $(hs[i]).text();
|
var text = $(hs[i]).text();
|
||||||
var tagName = hs[i].tagName.toLowerCase();
|
var tagName = hs[i].tagName.toLowerCase();
|
||||||
// scrollTo在page.js中定义
|
// scrollTo在page.js中定义
|
||||||
titles += '<li class="nav-' + tagName + '"><a data-a="' + tagName + '-' + encodeURI(text)+'" onclick="scrollTo(this, \'' + tagName + '\', \'' + text + '\')">' + text + '</a></li>';
|
titles += '<li class="nav-' + tagName + '"><a data-a="' + tagName + '-' + encodeURI(text)+'" onclick="window.scrollTo(this, \'' + tagName + '\', \'' + text + '\')">' + text + '</a></li>';
|
||||||
}
|
}
|
||||||
titles += "</ul>";
|
titles += "</ul>";
|
||||||
$("#leanoteNavContent").html(titles).height("auto"); // auto
|
$("#leanoteNavContent").html(titles).height("auto"); // auto
|
||||||
|
@ -1 +1 @@
|
|||||||
tinymce.PluginManager.add("leanote_nav",function(a){function b(){var b=a.getBody(),d=$(b),e=d.html();if(c!=e){c=e;for(var f=d.find("h1,h2,h3,h4,h5,h6").toArray(),g="<ul>",h=0;h<f.length;++h){var i=$(f[h]).text(),j=f[h].tagName.toLowerCase();g+='<li class="nav-'+j+'"><a data-a="'+j+"-"+encodeURI(i)+'" onclick="scrollTo(this, \''+j+"', '"+i+"')\">"+i+"</a></li>"}g+="</ul>",$("#leanoteNavContent").html(g).height("auto"),f.length||$("#leanoteNavContent").html(" Nothing...");var k=$("#leanoteNavContent").height(),l=$("#editorContent").height()-29;k>l&&$("#leanoteNavContent").height(l)}}var c="";a.on("init",function(){window.setTimeout(function(){b(),a.on("setcontent beforeadd undo paste",b),a.on("ExecCommand",function(a){b()}),a.on("click",function(a){b(),$("body").trigger("click")})},0)})});
|
tinymce.PluginManager.add("leanote_nav",function(a){function b(){var b=a.getBody(),d=$(b),e=d.html();if(c!=e){c=e;for(var f=d.find("h1,h2,h3,h4,h5,h6").toArray(),g="<ul>",h=0;h<f.length;++h){var i=$(f[h]).text(),j=f[h].tagName.toLowerCase();g+='<li class="nav-'+j+'"><a data-a="'+j+"-"+encodeURI(i)+'" onclick="window.scrollTo(this, \''+j+"', '"+i+"')\">"+i+"</a></li>"}g+="</ul>",$("#leanoteNavContent").html(g).height("auto"),f.length||$("#leanoteNavContent").html(" Nothing...");var k=$("#leanoteNavContent").height(),l=$("#editorContent").height()-29;k>l&&$("#leanoteNavContent").height(l)}}var c="";a.on("init",function(){window.setTimeout(function(){b(),a.on("setcontent beforeadd undo paste",b),a.on("ExecCommand",function(a){b()}),a.on("click",function(a){b(),$("body").trigger("click")})},0)})});
|
@ -621,6 +621,8 @@ define("tinymce/pasteplugin/Clipboard", [
|
|||||||
|
|
||||||
if (hasImage(e)) {
|
if (hasImage(e)) {
|
||||||
removePasteBin();
|
removePasteBin();
|
||||||
|
// 不然会在内容中插入一个图片,
|
||||||
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user