1
0
mirror of https://github.com/kataras/iris.git synced 2026-02-28 21:45:57 +00:00

Version 3.0.0-beta cleaned

This commit is contained in:
Makis Maropoulos
2016-05-30 17:08:09 +03:00
commit c26668a489
114 changed files with 14552 additions and 0 deletions

45
plugin/editor/README.md Normal file
View File

@@ -0,0 +1,45 @@
## Package information
Editor Plugin is just a bridge between Iris and [alm-tools](http://alm.tools).
[alm-tools](http://alm.tools) is a typescript online IDE/Editor, made by [@basarat](https://twitter.com/basarat) one of the top contributors of the [Typescript](http://www.typescriptlang.org).
Iris gives you the opportunity to edit your client-side using the alm-tools editor, via the editor plugin.
This plugin starts it's own server, if Iris server is using TLS then the editor will use the same key and cert.
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/editor"
)
func main(){
e := editor.New("username","password").Port(4444).Dir("/path/to/the/client/side/directory")
iris.Plugins().Add(e)
iris.Get("/", func (ctx *iris.Context){})
iris.Listen(":8080")
}
```
> Note for username, password: The Authorization specifies the authentication mechanism (in this case Basic) followed by the username and password.
Although, the string aHR0cHdhdGNoOmY= may look encrypted it is simply a base64 encoded version of <username>:<password>.
Would be readily available to anyone who could intercept the HTTP request. [Read more.](https://www.httpwatch.com/httpgallery/authentication/)
> The editor can't work if the directory doesn't contains a [tsconfig.json](http://www.typescriptlang.org/docs/handbook/tsconfig.json.html).
> If you are using the [typescript plugin](https://github.com/kataras/iris/tree/development/plugin/typescript) you don't have to call the .Dir(...)

157
plugin/editor/editor.go Normal file
View File

@@ -0,0 +1,157 @@
package editor
/* Notes for Auth
The Authorization specifies the authentication mechanism (in this case Basic) followed by the username and password.
Although, the string aHR0cHdhdGNoOmY= may look encrypted it is simply a base64 encoded version of <username>:<password>.
Would be readily available to anyone who could intercept the HTTP request.
*/
import (
"os"
"strconv"
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/npm"
"github.com/kataras/iris/utils"
)
const (
// Name the name of the Plugin, which is "EditorPlugin"
Name = "EditorPlugin"
)
type (
// Plugin is an Editor Plugin the struct which implements the iris.IPlugin
// it holds a logger from the iris' station
// username,password for basic auth
// directory which the client side code is
// keyfile,certfile for TLS listening
// and a host which is listening for
Plugin struct {
config *config.Editor
logger *logger.Logger
enabled bool // default true
keyfile string
certfile string
// after alm started
process *os.Process
}
)
// New creates and returns an Editor Plugin instance
func New(cfg ...config.Editor) *Plugin {
c := config.DefaultEditor().Merge(cfg)
e := &Plugin{enabled: true, config: &c}
return e
}
// User set a user, accepts two parameters: username (string), string (string)
func (e *Plugin) User(username string, password string) *Plugin {
e.config.Username = username
e.config.Password = password
return e
}
// Dir sets the directory which the client side source code alive
func (e *Plugin) Dir(workingDir string) *Plugin {
e.config.WorkingDir = workingDir
return e
}
// Port sets the port (int) for the editor plugin's standalone server
func (e *Plugin) Port(port int) *Plugin {
e.config.Port = port
return e
}
//
// SetEnable if true enables the editor plugin, otherwise disables it
func (e *Plugin) SetEnable(enable bool) {
e.enabled = enable
}
// GetName returns the name of the Plugin
func (e *Plugin) GetName() string {
return Name
}
// GetDescription EditorPlugin is a bridge between Iris and the alm-tools, the browser-based IDE for client-side sources.
func (e *Plugin) GetDescription() string {
return Name + " is a bridge between Iris and the alm-tools, the browser-based IDE for client-side sources. \n"
}
// PreListen runs before the server's listens, saves the keyfile,certfile and the host from the Iris station to listen for
func (e *Plugin) PreListen(s *iris.Iris) {
e.logger = s.Logger()
e.keyfile = s.Server().Config.KeyFile
e.certfile = s.Server().Config.CertFile
if e.config.Host == "" {
h := s.Server().Config.ListeningAddr
if idx := strings.Index(h, ":"); idx >= 0 {
h = h[0:idx]
}
if h == "" {
h = "127.0.0.1"
}
e.config.Host = h
}
e.start()
}
// PreClose kills the editor's server when Iris is closed
func (e *Plugin) PreClose(s *iris.Iris) {
if e.process != nil {
err := e.process.Kill()
if err != nil {
e.logger.Printf("\nError while trying to terminate the (Editor)Plugin, please kill this process by yourself, process id: %d", e.process.Pid)
}
}
}
// start starts the job
func (e *Plugin) start() {
if e.config.Username == "" || e.config.Password == "" {
e.logger.Println("Error before running alm-tools. You have to set username & password for security reasons, otherwise this plugin won't run.")
return
}
if !npm.Exists("alm/bin/alm") {
e.logger.Println("Installing alm-tools, please wait...")
res := npm.Install("alm")
if res.Error != nil {
e.logger.Print(res.Error.Error())
return
}
e.logger.Print(res.Message)
}
cmd := utils.CommandBuilder("node", npm.Abs("alm/src/server.js"))
cmd.AppendArguments("-a", e.config.Username+":"+e.config.Password, "-h", e.config.Host, "-t", strconv.Itoa(e.config.Port), "-d", e.config.WorkingDir[0:len(e.config.WorkingDir)-1])
// for auto-start in the browser: cmd.AppendArguments("-o")
if e.keyfile != "" && e.certfile != "" {
cmd.AppendArguments("--httpskey", e.keyfile, "--httpscert", e.certfile)
}
//For debug only:
//cmd.Stdout = os.Stdout
//cmd.Stderr = os.Stderr
//os.Stdin = os.Stdin
err := cmd.Start()
if err != nil {
e.logger.Println("Error while running alm-tools. Trace: " + err.Error())
return
}
//we lose the internal error handling but ok...
e.logger.Printf("Editor is running at %s:%d | %s", e.config.Host, e.config.Port, e.config.WorkingDir)
}

View File

@@ -0,0 +1,48 @@
## Iris Control
### THIS IS NOT READY YET
This plugin will give you remotely ( and local ) access to your iris server's information via a web interface
### Assets
No assets here because this is go -getable folder I don't want to messup with the folder size, in order to solve this
I created a downloader manager inside this package which downloads the first time the assets and unzip them to the kataras/iris/plugin/iris-control/iris-control-assets/ .
The assets files are inside [this repository](https://github.com/iris-contrib/iris-control-assets)
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/iriscontrol"
"fmt"
)
func main() {
iris.Plugins().Add(iriscontrol.Web(9090, map[string]string{
"irisusername1": "irispassword1",
"irisusername2": "irispassowrd2",
}))
iris.Get("/", func(ctx *iris.Context) {
})
iris.Post("/something", func(ctx *iris.Context) {
})
fmt.Printf("Iris is listening on :%d", 8080)
iris.Listen(":8080")
}
```

View File

@@ -0,0 +1,105 @@
package iriscontrol
import (
"os"
"strconv"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/routesinfo"
)
var pathSeperator = string(os.PathSeparator)
var pluginPath = os.Getenv("GOPATH") + pathSeperator + "src" + pathSeperator + "github.com" + pathSeperator + "kataras" + pathSeperator + "iris" + pathSeperator + "plugin" + pathSeperator + "iriscontrol" + pathSeperator
var assetsURL = "https://github.com/iris-contrib/iris-control-assets/archive/master.zip"
var assetsFolderName = "iris-control-assets-master"
var installationPath = pluginPath + assetsFolderName + pathSeperator
// for the plugin server
func (i *irisControlPlugin) startControlPanel() {
// install the assets first
if err := i.installAssets(); err != nil {
i.pluginContainer.Printf("[%s] %s Error %s: Couldn't install the assets from the internet,\n make sure you are connecting to the internet the first time running the iris-control plugin", time.Now().UTC().String(), Name, err.Error())
i.Destroy()
return
}
i.server = iris.New()
i.server.Config().Render.Template.Directory = installationPath + "templates"
//i.server.SetRenderConfig(i.server.Config.Render)
i.setPluginsInfo()
i.setPanelRoutes()
go i.server.Listen(strconv.Itoa(i.options.Port))
i.pluginContainer.Printf("[%s] %s is running at port %d with %d authenticated users", time.Now().UTC().String(), Name, i.options.Port, len(i.auth.authenticatedUsers))
}
// DashboardPage is the main data struct for the index
// contains a boolean if server is running, the routes and the plugins
type DashboardPage struct {
ServerIsRunning bool
Routes []routesinfo.RouteInfo
Plugins []PluginInfo
}
func (i *irisControlPlugin) setPluginsInfo() {
plugins := i.pluginContainer.GetAll()
i.plugins = make([]PluginInfo, 0, len(plugins))
for _, plugin := range plugins {
i.plugins = append(i.plugins, PluginInfo{Name: i.pluginContainer.GetName(plugin), Description: i.pluginContainer.GetDescription(plugin)})
}
}
// installAssets checks if must install ,if yes download the zip and unzip it, returns error.
func (i *irisControlPlugin) installAssets() (err error) {
//we know already what is the zip folder inside it, so we can check if it's exists, if yes then don't install it again.
if i.pluginContainer.GetDownloader().DirectoryExists(installationPath) {
return
}
//set the installationPath ,although we know it but do it here too
installationPath, err = i.pluginContainer.GetDownloader().Install(assetsURL, pluginPath)
return err
}
func (i *irisControlPlugin) setPanelRoutes() {
i.server.Static("/public", installationPath+"static", 1)
i.server.Get("/login", func(ctx *iris.Context) {
ctx.Render("login", nil)
})
i.server.Post("/login", func(ctx *iris.Context) {
i.auth.login(ctx)
})
i.server.Use(i.auth)
i.server.Get("/", func(ctx *iris.Context) {
ctx.Render("index", DashboardPage{ServerIsRunning: i.station.Server().IsListening(), Routes: i.routes.All(), Plugins: i.plugins})
})
i.server.Post("/logout", func(ctx *iris.Context) {
i.auth.logout(ctx)
})
//the controls
i.server.Post("/start_server", func(ctx *iris.Context) {
//println("server start")
old := i.stationServer
if !old.IsSecure() {
i.station.Listen(old.Config.ListeningAddr)
//yes but here it does re- post listen to this plugin so ...
} else {
i.station.ListenTLS(old.Config.ListeningAddr, old.Config.CertFile, old.Config.KeyFile)
}
})
i.server.Post("/stop_server", func(ctx *iris.Context) {
//println("server stop")
i.station.Close()
})
}

View File

@@ -0,0 +1,11 @@
package iriscontrol
// NOT READY YET
// PluginInfo holds the Name and the description of the registed plugins
type PluginInfo struct {
Name string
Description string
}
//func getPluginlist...

View File

@@ -0,0 +1,112 @@
package iriscontrol
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/plugin/routesinfo"
"github.com/kataras/iris/server"
)
// Name the name(string) of this plugin which is Iris Control
const Name = "Iris Control"
type irisControlPlugin struct {
options config.IrisControl
// the pluginContainer is the container which keeps this plugin from the main user's iris instance
pluginContainer iris.IPluginContainer
// the station object of the main user's iris instance
station *iris.Iris
//a copy of the server which the main user's iris is listening for
stationServer *server.Server
// the server is this plugin's server object, it is managed by this plugin only
server *iris.Iris
//
//infos
routes *routesinfo.Plugin
plugins []PluginInfo
//
auth *userAuth
}
// New returns the plugin which is ready-to-use inside iris.Plugin method
// receives config.IrisControl
func New(cfg ...config.IrisControl) iris.IPlugin {
c := config.DefaultIrisControl()
if len(cfg) > 0 {
c = cfg[0]
}
auth := newUserAuth(c.Users)
if auth == nil {
panic(Name + " Error: you should pass authenticated users map to the options, refer to the docs!")
}
return &irisControlPlugin{options: c, auth: auth, routes: routesinfo.RoutesInfo()}
}
// Web set the options for the plugin and return the plugin which is ready-to-use inside iris.Plugin method
// first parameter is port
// second parameter is map of users (username:password)
func Web(port int, users map[string]string) iris.IPlugin {
return New(config.IrisControl{port, users})
}
// implement the base IPlugin
func (i *irisControlPlugin) Activate(container iris.IPluginContainer) error {
i.pluginContainer = container
container.Add(i.routes) // add the routesinfo plugin to the main server
return nil
}
func (i irisControlPlugin) GetName() string {
return Name
}
func (i irisControlPlugin) GetDescription() string {
return Name + " is just a web interface which gives you control of your Iris.\n"
}
//
// implement the rest of the plugin
// PostHandle
func (i *irisControlPlugin) PostHandle(route iris.IRoute) {
}
// PostListen sets the station object after the main server starts
// starts the actual work of the plugin
func (i *irisControlPlugin) PostListen(s *iris.Iris) {
//if the first time, because other times start/stop of the server so listen and no listen will be only from the control panel
if i.station == nil {
i.station = s
i.stationServer = i.station.Server()
i.startControlPanel()
}
}
func (i *irisControlPlugin) PreClose(s *iris.Iris) {
// Do nothing. This is a wrapper of the main server if we destroy when users stop the main server then we cannot continue the control panel i.Destroy()
}
//
// Destroy removes entirely the plugin, the options and all of these properties, you cannot re-use this plugin after this method.
func (i *irisControlPlugin) Destroy() {
i.pluginContainer.Remove(Name)
i.options = config.IrisControl{}
i.routes = nil
i.station = nil
i.server.Close()
i.pluginContainer = nil
i.auth.Destroy()
i.auth = nil
i.pluginContainer.Printf("[%s] %s is turned off", time.Now().UTC().String(), Name)
}

View File

@@ -0,0 +1,20 @@
package iriscontrol
// for the main server
func (i *irisControlPlugin) StartServer() {
if i.station.Server().IsListening() == false {
if i.station.Server().IsSecure() {
//listen with ListenTLS
i.station.ListenTLS(i.station.Server().Config.ListeningAddr, i.station.Server().Config.CertFile, i.station.Server().Config.KeyFile)
} else {
//listen normal
i.station.Listen(i.station.Server().Config.ListeningAddr)
}
}
}
func (i *irisControlPlugin) StopServer() {
if i.station.Server().IsListening() {
i.station.Close()
}
}

View File

@@ -0,0 +1,97 @@
package iriscontrol
import (
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
// _ empty because it auto-registers
_ "github.com/kataras/iris/sessions/providers/memory"
)
var panelSessions *sessions.Manager
func init() {
//using the default
panelSessions = sessions.New()
}
type user struct {
username string
password string
}
type userAuth struct {
authenticatedUsers []user
}
// newUserAuth returns a new userAuth object, parameter is the authenticated users as map
func newUserAuth(usersMap map[string]string) *userAuth {
if usersMap != nil {
obj := &userAuth{make([]user, 0)}
for key, val := range usersMap {
obj.authenticatedUsers = append(obj.authenticatedUsers, user{key, val})
}
return obj
}
return nil
}
func (u *userAuth) login(ctx *iris.Context) {
session := panelSessions.Start(ctx)
username := ctx.PostFormValue("username")
password := ctx.PostFormValue("password")
for _, authenticatedUser := range u.authenticatedUsers {
if authenticatedUser.username == username && authenticatedUser.password == password {
session.Set("username", username)
session.Set("password", password)
ctx.Write("success")
return
}
}
ctx.Write("fail")
}
func (u *userAuth) logout(ctx *iris.Context) {
session := panelSessions.Start(ctx)
session.Set("user", nil)
ctx.Redirect("/login")
}
// check if session stored, then check if this user is the correct, each time, then continue, else not
func (u *userAuth) Serve(ctx *iris.Context) {
if ctx.PathString() == "/login" || strings.HasPrefix(ctx.PathString(), "/public") {
ctx.Next()
return
}
session := panelSessions.Start(ctx)
if sessionVal := session.Get("username"); sessionVal != nil {
username := sessionVal.(string)
password := session.GetString("password")
if username != "" && password != "" {
for _, authenticatedUser := range u.authenticatedUsers {
if authenticatedUser.username == username && authenticatedUser.password == password {
ctx.Next()
return
}
}
}
}
//if not logged in the redirect to the /login
ctx.Redirect("/login")
}
// Destroy this is called on PreClose by the iriscontrol.go
func (u *userAuth) Destroy() {
}

View File

@@ -0,0 +1,61 @@
## RoutesInfo plugin
This plugin collects & stores all registered routes and gives information about them.
#### The RouteInfo
```go
type RouteInfo struct {
Method string
Domain string
Path string
RegistedAt time.Time
}
```
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/routesinfo"
)
func main() {
info := routesinfo.New()
iris.Plugins().Add(info)
iris.Get("/yourpath", func(c *iris.Context) {
c.Write("yourpath")
})
iris.Post("/otherpostpath", func(c *iris.Context) {
c.Write("other post path")
})
all := info.All()
// allget := info.ByMethod("GET") -> slice
// alllocalhost := info.ByDomain("localhost") -> slice
// bypath:= info.ByPath("/yourpath") -> slice
// bydomainandmethod:= info.ByDomainAndMethod("localhost","GET") -> slice
// bymethodandpath:= info.ByMethodAndPath("GET","/yourpath") -> single (it could be slice for all domains too but it's not)
println("The first registed route was: ", all[0].Path, "registed at: ", all[0].RegistedAt.String())
println("All routes info:")
for i:= range all {
println(all[i].String())
//outputs->
// Domain: localhost Method: GET Path: /yourpath RegistedAt: 2016/03/27 15:27:05:029 ...
// Domain: localhost Method: POST Path: /otherpostpath RegistedAt: 2016/03/27 15:27:05:030 ...
}
iris.Listen(":8080")
}
```

View File

@@ -0,0 +1,151 @@
package routesinfo
import (
"fmt"
"strings"
"time"
"github.com/kataras/iris"
)
//Name the name of the plugin, is "RoutesInfo"
const Name = "RoutesInfo"
// RouteInfo holds the method, domain, path and registered time of a route
type RouteInfo struct {
Method string
Domain string
Path string
RegistedAt time.Time
}
// String returns the string presentation of the Route(Info)
func (ri RouteInfo) String() string {
if ri.Domain == "" {
ri.Domain = "localhost" // only for printing, this doesn't save it, no pointer.
}
return fmt.Sprintf("Domain: %s Method: %s Path: %s RegistedAt: %s", ri.Domain, ri.Method, ri.Path, ri.RegistedAt.String())
}
// Plugin the routes info plugin, holds the routes as RouteInfo objects
type Plugin struct {
routes []RouteInfo
}
// implement the base IPlugin
// GetName ...
func (r Plugin) GetName() string {
return Name
}
// GetDescription RoutesInfo gives information about the registed routes
func (r Plugin) GetDescription() string {
return Name + " gives information about the registed routes.\n"
}
//
// implement the rest of the plugin
// PostHandle collect the registed routes information
func (r *Plugin) PostHandle(route iris.IRoute) {
if r.routes == nil {
r.routes = make([]RouteInfo, 0)
}
r.routes = append(r.routes, RouteInfo{route.GetMethod(), route.GetDomain(), route.GetPath(), time.Now()})
}
// All returns all routeinfos
// returns a slice
func (r Plugin) All() []RouteInfo {
return r.routes
}
// ByDomain returns all routeinfos which registed to a specific domain
// returns a slice, if nothing founds this slice has 0 len&cap
func (r Plugin) ByDomain(domain string) []RouteInfo {
var routesByDomain []RouteInfo
rlen := len(r.routes)
if domain == "localhost" || domain == "127.0.0.1" || domain == ":" {
domain = ""
}
for i := 0; i < rlen; i++ {
if r.routes[i].Domain == domain {
routesByDomain = append(routesByDomain, r.routes[i])
}
}
return routesByDomain
}
// ByMethod returns all routeinfos by a http method
// returns a slice, if nothing founds this slice has 0 len&cap
func (r Plugin) ByMethod(method string) []RouteInfo {
var routesByMethod []RouteInfo
rlen := len(r.routes)
method = strings.ToUpper(method)
for i := 0; i < rlen; i++ {
if r.routes[i].Method == method {
routesByMethod = append(routesByMethod, r.routes[i])
}
}
return routesByMethod
}
// ByPath returns all routeinfos by a path
// maybe one path is the same on GET and POST ( for example /login GET, /login POST)
// because of that it returns a slice and not only one RouteInfo
// returns a slice, if nothing founds this slice has 0 len&cap
func (r Plugin) ByPath(path string) []RouteInfo {
var routesByPath []RouteInfo
rlen := len(r.routes)
for i := 0; i < rlen; i++ {
if r.routes[i].Path == path {
routesByPath = append(routesByPath, r.routes[i])
}
}
return routesByPath
}
// ByDomainAndMethod returns all routeinfos registed to a specific domain and has specific http method
// returns a slice, if nothing founds this slice has 0 len&cap
func (r Plugin) ByDomainAndMethod(domain string, method string) []RouteInfo {
var routesByDomainAndMethod []RouteInfo
rlen := len(r.routes)
method = strings.ToUpper(method)
if domain == "localhost" || domain == "127.0.0.1" || domain == ":" {
domain = ""
}
for i := 0; i < rlen; i++ {
if r.routes[i].Method == method && r.routes[i].Domain == domain {
routesByDomainAndMethod = append(routesByDomainAndMethod, r.routes[i])
}
}
return routesByDomainAndMethod
}
// ByMethodAndPath returns a single *RouteInfo which has specific http method and path
// returns only the first match
// if nothing founds returns nil
func (r Plugin) ByMethodAndPath(method string, path string) *RouteInfo {
rlen := len(r.routes)
for i := 0; i < rlen; i++ {
if r.routes[i].Method == method && r.routes[i].Path == path {
return &r.routes[i]
}
}
return nil
}
//
// RoutesInfo returns the Plugin, same as New()
func RoutesInfo() *Plugin {
return &Plugin{}
}
// New returns the Plugin, same as RoutesInfo()
func New() *Plugin {
return &Plugin{}
}

View File

@@ -0,0 +1,83 @@
## Package information
This is an Iris and typescript bridge plugin.
1. Search for typescript files (.ts)
2. Search for typescript projects (.tsconfig)
3. If 1 || 2 continue else stop
4. Check if typescript is installed, if not then auto-install it (always inside npm global modules, -g)
5. If typescript project then build the project using tsc -p $dir
6. If typescript files and no project then build each typescript using tsc $filename
7. Watch typescript files if any changes happens, then re-build (5|6)
> Note: Ignore all typescript files & projects whose path has '/node_modules/'
## Options
This plugin has **optionally** options
1. Bin: string, the typescript installation path/bin/tsc or tsc.cmd, if empty then it will search to the global npm modules
2. Dir: string, Dir set the root, where to search for typescript files/project. Default "./"
3. Ignore: string, comma separated ignore typescript files/project from these directories. Default "" (node_modules are always ignored)
4. Tsconfig: &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
5. Editor: typescript.Editor(), if setted then alm-tools browser-based typescript IDE will be available. Defailt is nil
> Note: if any string in Ignore doesn't start with './' then it will ignore all files which contains this path string.
For example /node_modules/ will ignore all typescript files that are inside at ANY '/node_modules/', that means and the submodules.
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/typescript"
)
func main(){
/* Options
Bin -> the typescript installation path/bin/tsc or tsc.cmd, if empty then it will search to the global npm modules
Dir -> where to search for typescript files/project. Default "./"
Ignore -> comma separated ignore typescript files/project from these directories (/node_modules/ are always ignored). Default ""
Tsconfig -> &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
Editor -> typescript.Editor(), if setted then alm-tools browser-based typescript IDE will be available. Default is nil.
*/
ts := typescript.Options {
Dir: "./scripts/src",
Tsconfig: &typescript.Tsconfig{Module: "commonjs", Target: "es5"}, // or typescript.DefaultTsconfig()
}
//if you want to change only certain option(s) but you want default to all others then you have to do this:
ts = typescript.DefaultOptions()
//
iris.Plugins().Add(typescript.New(ts)) //or with the default options just: typescript.New()
iris.Get("/", func (ctx *iris.Context){})
iris.Listen(":8080")
}
```
## Editor
[alm-tools](http://alm.tools) is a typescript online IDE/Editor, made by [@basarat](https://twitter.com/basarat) one of the top contributors of the [Typescript](http://www.typescriptlang.org).
Iris gives you the opportunity to edit your client-side using the alm-tools editor, via the editor plugin.
With typescript plugin you have to set the Editor option and you're ready:
```go
typescript.Options {
//...
Editor: typescript.Editor("username","passowrd")
//...
}
```
> [Read more](https://github.com/kataras/iris/tree/development/plugin/editor) for Editor

View File

@@ -0,0 +1,102 @@
package typescript
import (
"encoding/json"
"io/ioutil"
"reflect"
)
type (
// Tsconfig the struct for tsconfig.json
Tsconfig struct {
CompilerOptions CompilerOptions `json:"compilerOptions"`
Exclude []string `json:"exclude"`
}
// CompilerOptions contains all the compiler options used by the tsc (typescript compiler)
CompilerOptions struct {
Declaration bool `json:"declaration"`
Module string `json:"module"`
Target string `json:"target"`
Watch bool `json:"watch"`
Charset string `json:"charset"`
Diagnostics bool `json:"diagnostics"`
EmitBOM bool `json:"emitBOM"`
EmitDecoratorMetadata bool `json:"emitDecoratorMetadata"`
ExperimentalDecorators bool `json:"experimentalDecorators"`
InlineSourceMap bool `json:"inlineSourceMap"`
InlineSources bool `json:"inlineSources"`
IsolatedModules bool `json:"isolatedModules"`
Jsx string `json:"jsx"`
ReactNamespace string `json:"reactNamespace"`
ListFiles bool `json:"listFiles"`
Locale string `json:"locale"`
MapRoot string `json:"mapRoot"`
ModuleResolution string `json:"moduleResolution"`
NewLine string `json:"newLine"`
NoEmit bool `json:"noEmit"`
NoEmitOnError bool `json:"noEmitOnError"`
NoEmitHelpers bool `json:"noEmitHelpers"`
NoImplicitAny bool `json:"noImplicitAny"`
NoLib bool `json:"noLib"`
NoResolve bool `json:"noResolve"`
SkipDefaultLibCheck bool `json:"skipDefaultLibCheck"`
OutDir string `json:"outDir"`
OutFile string `json:"outFile"`
PreserveConstEnums bool `json:"preserveConstEnums"`
Pretty bool `json:"pretty"`
RemoveComments bool `json:"removeComments"`
RootDir string `json:"rootDir"`
SourceMap bool `json:"sourceMap"`
SourceRoot string `json:"sourceRoot"`
StripInternal bool `json:"stripInternal"`
SuppressExcessPropertyErrors bool `json:"suppressExcessPropertyErrors"`
SuppressImplicitAnyIndexErrors bool `json:"suppressImplicitAnyIndexErrors"`
AllowUnusedLabels bool `json:"allowUnusedLabels"`
NoImplicitReturns bool `json:"noImplicitReturns"`
NoFallthroughCasesInSwitch bool `json:"noFallthroughCasesInSwitch"`
AllowUnreachableCode bool `json:"allowUnreachableCode"`
ForceConsistentCasingInFileNames bool `json:"forceConsistentCasingInFileNames"`
AllowSyntheticDefaultImports bool `json:"allowSyntheticDefaultImports"`
AllowJs bool `json:"allowJs"`
NoImplicitUseStrict bool `json:"noImplicitUseStrict"`
}
)
// CompilerArgs returns the CompilerOptions' contents of the Tsconfig
// it reads the json tags, add '--' at the start of each one and returns an array of strings
func (tsconfig *Tsconfig) CompilerArgs() []string {
val := reflect.ValueOf(tsconfig).Elem().FieldByName("CompilerOptions")
compilerOpts := make([]string, val.NumField())
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i)
compilerOpts[i] = "--" + typeField.Tag.Get("json")
}
return compilerOpts
}
// FromFile reads a file & returns the Tsconfig by its contents
func FromFile(tsConfigAbsPath string) *Tsconfig {
file, err := ioutil.ReadFile(tsConfigAbsPath)
if err != nil {
panic("[IRIS TypescriptPlugin.FromFile]" + err.Error())
}
config := &Tsconfig{}
json.Unmarshal(file, config)
return config
}
// DefaultTsconfig returns the default Tsconfig, with CompilerOptions module: commonjs, target: es5 and ignore the node_modules
func DefaultTsconfig() *Tsconfig {
return &Tsconfig{
CompilerOptions: CompilerOptions{
Module: "commonjs",
Target: "es5",
NoImplicitAny: false,
SourceMap: false,
},
Exclude: []string{"node_modules"},
}
}

View File

@@ -0,0 +1,300 @@
package typescript
import (
"errors"
"os"
"path/filepath"
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/npm"
"github.com/kataras/iris/plugin/editor"
"github.com/kataras/iris/utils"
)
/* Notes
The editor is working when the typescript plugin finds a typescript project (tsconfig.json),
also working only if one typescript project found (normaly is one for client-side).
*/
// Name the name of the plugin, is "TypescriptPlugin"
const Name = "TypescriptPlugin"
var nodeModules = utils.PathSeparator + "node_modules" + utils.PathSeparator
type (
// Options the struct which holds the TypescriptPlugin options
// Has five (5) fields
//
// 1. Bin: string, the typescript installation directory/typescript/lib/tsc.js, if empty it will search inside global npm modules
// 2. Dir: string, Dir set the root, where to search for typescript files/project. Default "./"
// 3. Ignore: string, comma separated ignore typescript files/project from these directories. Default "" (node_modules are always ignored)
// 4. Tsconfig: &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
// 5. Editor: typescript.Editor("username","password"), if setted then alm-tools browser-based typescript IDE will be available. Defailt is nil
Options struct {
Bin string
Dir string
Ignore string
Tsconfig *Tsconfig
Editor *editor.Plugin // the editor is just a plugin also
}
// Plugin the struct of the Typescript Plugin, holds all necessary fields & methods
Plugin struct {
options Options
// taken from Activate
pluginContainer iris.IPluginContainer
// taken at the PreListen
logger *logger.Logger
}
)
// Editor is just a shortcut for github.com/kataras/iris/plugin/editor.New()
// returns a new (Editor)Plugin, it's exists here because the typescript plugin has direct interest with the EditorPlugin
func Editor(username, password string) *editor.Plugin {
editorCfg := config.DefaultEditor()
editorCfg.Username = username
editorCfg.Password = password
return editor.New(editorCfg)
}
// DefaultOptions returns the default Options of the Plugin
func DefaultOptions() Options {
root, err := os.Getwd()
if err != nil {
panic("Typescript Plugin: Cannot get the Current Working Directory !!! [os.getwd()]")
}
opt := Options{Dir: root + utils.PathSeparator, Ignore: nodeModules, Tsconfig: DefaultTsconfig()}
opt.Bin = npm.Abs("typescript/lib/tsc.js")
return opt
}
// Plugin
// New creates & returns a new instnace typescript plugin
func New(_opt ...Options) *Plugin {
var options = DefaultOptions()
if _opt != nil && len(_opt) > 0 { //not nil always but I like this way :)
opt := _opt[0]
if opt.Bin != "" {
options.Bin = opt.Bin
}
if opt.Dir != "" {
options.Dir = opt.Dir
}
if !strings.Contains(opt.Ignore, nodeModules) {
opt.Ignore += "," + nodeModules
}
if opt.Tsconfig != nil {
options.Tsconfig = opt.Tsconfig
}
options.Ignore = opt.Ignore
}
return &Plugin{options: options}
}
// implement the IPlugin & IPluginPreListen
// Activate ...
func (t *Plugin) Activate(container iris.IPluginContainer) error {
t.pluginContainer = container
return nil
}
// GetName ...
func (t *Plugin) GetName() string {
return Name + "[" + utils.RandomString(10) + "]" // this allows the specific plugin to be registed more than one time
}
// GetDescription TypescriptPlugin scans and compile typescript files with ease
func (t *Plugin) GetDescription() string {
return Name + " scans and compile typescript files with ease. \n"
}
// PreListen ...
func (t *Plugin) PreListen(s *iris.Iris) {
t.logger = s.Logger()
t.start()
}
//
// implementation
func (t *Plugin) start() {
defaultCompilerArgs := t.options.Tsconfig.CompilerArgs() //these will be used if no .tsconfig found.
if t.hasTypescriptFiles() {
//Can't check if permission denied returns always exists = true....
//typescriptModule := out + string(os.PathSeparator) + "typescript" + string(os.PathSeparator) + "bin"
if !npm.Exists(t.options.Bin) {
t.logger.Println("Installing typescript, please wait...")
res := npm.Install("typescript")
if res.Error != nil {
t.logger.Print(res.Error.Error())
return
}
t.logger.Print(res.Message)
}
projects := t.getTypescriptProjects()
if len(projects) > 0 {
watchedProjects := 0
//typescript project (.tsconfig) found
for _, project := range projects {
cmd := utils.CommandBuilder("node", t.options.Bin, "-p", project[0:strings.LastIndex(project, utils.PathSeparator)]) //remove the /tsconfig.json)
projectConfig := FromFile(project)
if projectConfig.CompilerOptions.Watch {
watchedProjects++
// if has watch : true then we have to wrap the command to a goroutine (I don't want to use the .Start here)
go func() {
_, err := cmd.Output()
if err != nil {
t.logger.Println(err.Error())
return
}
}()
} else {
_, err := cmd.Output()
if err != nil {
t.logger.Println(err.Error())
return
}
}
}
t.logger.Printf("%d Typescript project(s) compiled ( %d monitored by a background file watcher ) ", len(projects), watchedProjects)
} else {
//search for standalone typescript (.ts) files and compile them
files := t.getTypescriptFiles()
if len(files) > 0 {
watchedFiles := 0
if t.options.Tsconfig.CompilerOptions.Watch {
watchedFiles = len(files)
}
//it must be always > 0 if we came here, because of if hasTypescriptFiles == true.
for _, file := range files {
cmd := utils.CommandBuilder("node", t.options.Bin)
cmd.AppendArguments(defaultCompilerArgs...)
cmd.AppendArguments(file)
_, err := cmd.Output()
cmd.Args = cmd.Args[0 : len(cmd.Args)-1] //remove the last, which is the file
if err != nil {
t.logger.Println(err.Error())
return
}
}
t.logger.Printf("%d Typescript file(s) compiled ( %d monitored by a background file watcher )", len(files), watchedFiles)
}
}
//editor activation
if len(projects) == 1 && t.options.Editor != nil {
dir := projects[0][0:strings.LastIndex(projects[0], utils.PathSeparator)]
t.options.Editor.Dir(dir)
t.pluginContainer.Add(t.options.Editor)
}
}
}
func (t *Plugin) hasTypescriptFiles() bool {
root := t.options.Dir
ignoreFolders := strings.Split(t.options.Ignore, ",")
hasTs := false
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
return nil
}
}
if strings.HasSuffix(path, ".ts") {
hasTs = true
return errors.New("Typescript found, hope that will stop here")
}
return nil
})
return hasTs
}
func (t *Plugin) getTypescriptProjects() []string {
var projects []string
ignoreFolders := strings.Split(t.options.Ignore, ",")
root := t.options.Dir
//t.logger.Printf("\nSearching for typescript projects in %s", root)
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
//t.logger.Println(path + " ignored")
return nil
}
}
if strings.HasSuffix(path, utils.PathSeparator+"tsconfig.json") {
//t.logger.Printf("\nTypescript project found in %s", path)
projects = append(projects, path)
}
return nil
})
return projects
}
// this is being called if getTypescriptProjects return 0 len, then we are searching for files using that:
func (t *Plugin) getTypescriptFiles() []string {
var files []string
ignoreFolders := strings.Split(t.options.Ignore, ",")
root := t.options.Dir
//t.logger.Printf("\nSearching for typescript files in %s", root)
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
//t.logger.Println(path + " ignored")
return nil
}
}
if strings.HasSuffix(path, ".ts") {
//t.logger.Printf("\nTypescript file found in %s", path)
files = append(files, path)
}
return nil
})
return files
}
//
//