├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .prettierignore
├── .travis.yml
├── COMPARISON.md
├── LICENSE
├── Makefile
├── README.md
├── assets
├── logo.png
└── logo.svg
├── bootstrap
├── include.go
└── snap.go
├── bundler
├── bundle.go
└── cache.go
├── cmd
└── root.go
├── core
├── README.md
├── console.go
├── console_test.go
├── console_util.go
├── dispatch.go
├── ops.go
├── ops
│ ├── env.go
│ ├── fetch.go
│ ├── fs.go
│ └── serve.go
├── options
│ └── options.go
├── plugins.go
├── plugins_win.go
├── repl.go
└── run.go
├── dev
├── dev.go
├── report.go
└── typescript.go
├── go.mod
├── go.sum
├── js
├── 00_core.js
├── 01_namespace.js
├── 02_console.js
├── 03_fetch.js
├── 04_event.js
├── 05_compiler.js
├── 06_encoder.js
├── 07_testing.js
└── 10_window.js
├── main.go
├── mod.toml
├── module
└── config.go
├── packager
├── exec.go
├── packager.go
└── template.go
├── std
├── README.md
├── async
│ ├── README.md
│ ├── deferred.ts
│ ├── deferred_test.ts
│ ├── delay.ts
│ ├── mux_async_iterator.ts
│ └── pool.ts
├── bytes
│ ├── README.md
│ └── bytes.ts
├── encoding
│ └── hex.ts
├── fmt
│ └── colors.ts
├── fs
│ └── exist.ts
├── hash
│ ├── aes.ts
│ ├── hasher.ts
│ ├── md5.ts
│ ├── sha1.ts
│ ├── sha256.ts
│ └── sha512.ts
└── uuid
│ ├── common.ts
│ ├── v1.ts
│ ├── v4.ts
│ └── v5.ts
├── testing
├── bench
│ ├── fs.js
│ ├── fs_deno.js
│ ├── fs_node.js
│ ├── http_deno.ts
│ ├── http_elsa.ts
│ └── pi.js
├── bundle
│ ├── basics.js
│ ├── basics.js.mini.out
│ ├── basics.js.out
│ ├── export.js
│ ├── hello.ts
│ ├── hello.ts.out
│ ├── local_imports.js
│ ├── local_imports.js.out
│ ├── served.ts
│ └── url.ts
├── bundle_test.go
├── core_test.go
├── exports
│ ├── 1.js
│ ├── 2.js
│ └── import.js
├── fs
│ ├── cwd_test.js
│ ├── exists_test.js
│ ├── readFile_test.js
│ ├── remove_test.js
│ ├── sample.txt
│ ├── stats_test.js
│ └── writeFile_test.js
├── http
│ └── serve.js
├── miscellaneous
│ ├── args.js
│ ├── args_test.js
│ ├── mode_test.js
│ └── while.js
├── ops
│ ├── event.js
│ └── pending_jobs.js
├── pkg_test.go
├── plugin
│ ├── plugin.js
│ └── test_plugin
│ │ └── plugin.go
├── test_server.go
├── tests
│ ├── mode_test.js
│ ├── test1_test.js
│ ├── test_test.js
│ └── utils.ts
├── typescript
│ ├── import.ts
│ ├── typeerror.ts
│ └── wrong.ts
└── web
│ ├── console_test.js
│ ├── encode_test.js
│ └── fetch_test.js
├── typescript
├── lib.es6.d.ts
└── typescript.js
└── util
├── check.go
└── log.go
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | indent_style = space
7 | indent_size = 2
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # ignore the bundled TypeScript source
2 | *.js linguist-vendored
3 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | build-mac-linux:
11 | name: Build ${{ matrix.os }}
12 |
13 | runs-on: ${{ matrix.os }}
14 | env:
15 | GOPATH: ${{ github.workspace }}
16 | strategy:
17 | matrix:
18 | os: [ubuntu-latest, macos-latest]
19 |
20 | steps:
21 | - name: Setup repo
22 | uses: actions/checkout@v2
23 | with:
24 | submodules: true
25 | path: "./src/github.com/elsaland/elsa"
26 |
27 | - name: Setup go
28 | uses: actions/setup-go@v2
29 |
30 | - name: Print go env
31 | run: go env
32 |
33 | - name: Install go-bindata
34 | run: go get github.com/go-bindata/go-bindata
35 |
36 | - name: Run bootstrap
37 | working-directory: ./src/github.com/elsaland/elsa
38 | run: go run ./bootstrap/
39 |
40 | - name: Build
41 | working-directory: ./src/github.com/elsaland/elsa
42 | run: go build --ldflags "-s -w" -o elsa-${{ matrix.os }} .
43 |
44 | - name: Test
45 | working-directory: ./src/github.com/elsaland/elsa
46 | if: startsWith(matrix.os,'ubuntu')
47 | run: |
48 | go test ./testing
49 | ./elsa-${{ matrix.os }} test --fs --net
50 |
51 | - name: Upload Elsa executables
52 | uses: actions/upload-artifact@v2
53 | with:
54 | name: elsa-${{ matrix.os }}
55 | path: ./src/github.com/elsaland/elsa/elsa-${{ matrix.os }}
56 |
57 | build-windows:
58 | name: Build Windows
59 |
60 | runs-on: windows-latest
61 | env:
62 | GOPATH: ${{ github.workspace }}
63 |
64 | steps:
65 | - name: Setup repo
66 | uses: actions/checkout@v2
67 | with:
68 | submodules: true
69 | path: "./src/github.com/elsaland/elsa"
70 |
71 | - name: Setup go
72 | uses: actions/setup-go@v2
73 |
74 | - name: Print go env
75 | run: go env
76 |
77 | - name: Install go-bindata
78 | run: go get github.com/go-bindata/go-bindata
79 |
80 | - name: Run bootstrap
81 | working-directory: ./src/github.com/elsaland/elsa
82 | run: go run ./bootstrap/
83 |
84 | - name: Build
85 | working-directory: ./src/github.com/elsaland/elsa
86 | run: go build --ldflags "-s -w" -o elsa-windows.exe .
87 |
88 | - name: Upload Elsa executables
89 | uses: actions/upload-artifact@v2
90 | with:
91 | name: elsa-windows-latest
92 | path: ./src/github.com/elsaland/elsa/elsa-windows.exe
93 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | .vscode
3 | .idea
4 |
5 | # testing files
6 | output.js
7 | benchmarks
8 | test.ts
9 |
10 | # executable
11 | elsa
12 | # executable on windows
13 | elsa.exe
14 |
15 | # build cache
16 | data.go
17 | target
18 |
19 | # third-party tools
20 | node_modules
21 |
22 | # macOS specific files
23 | .DS_Store
24 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | typescript
2 | target
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | arch:
4 | - amd64
5 | - arm64 # please note arm64-graviton2 requires explicit virt: [lxd|vm] tag so it's recommended for jobs.include, see below
6 | os: linux # different CPU architectures are only supported on Linux
7 |
8 | env:
9 | - GO111MODULE=on
10 |
11 | go:
12 | - 1.14.6
13 |
14 | go_import_path: github.com/elsaland/elsa
15 |
16 | script:
17 | - go get github.com/go-bindata/go-bindata
18 | - make build
19 |
--------------------------------------------------------------------------------
/COMPARISON.md:
--------------------------------------------------------------------------------
1 | ## Comparison
2 |
3 | | Features | Elsa (unreleased) | Deno v1.3.3 | Node.js v14.4.0 |
4 | | ------------------------ | ----------------- | ----------- | ---------------- |
5 | | Language | Go | Rust | C++ |
6 | | JS engine | **Quickjs** | V8 | V8 |
7 | | Bundler | Yes | Yes | No |
8 | | HTTP imports | Yes | Yes | No |
9 | | In-built package manager | No | No | Yes |
10 | | Compiling to executable | **Yes** | Yes | No (third party) |
11 | | Explicit imports | Yes | Yes | No |
12 | | Secure by default | Yes | Yes | No |
13 | | Binary size | **~12mb** \* | ~44mb \* | ~68mb |
14 |
15 | \* Lacks data for `Intl` and `toLocaleString`
16 |
17 | > The list is not complete and there is more to add, feel free to open a PR for the same.
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 The elsa.land team
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | go run ./bootstrap
3 | go build -o elsa .
4 |
5 | release:
6 | go run ./bootstrap
7 | go build --ldflags "-s -w" -o elsa .
8 |
9 | benchmark:
10 | # console benchmarks
11 | hyperfine './elsa run ./testing/web/console.js' 'deno run ./testing/web/console.js' 'node ./testing/console.js' -s full -r 100 --warmup 50 --export-json ./benchmarks/console.json
12 | # bundle benchmarks
13 | hyperfine './elsa bundle ./testing/web/console.js' 'deno bundle ./testing/web/console.js' -s full -r 100 --warmup 50 --export-json ./benchmarks/bundle.json -i
14 | # readFile benchmarks
15 | hyperfine './elsa run ./testing/bench/fs.js --fs' 'deno run --allow-read ./testing/bench/fs_deno.js' 'node ./testing/bench/fs_node.js' --warmup 100 -s full -r 100 --export-json ./benchmarks/fs.json
16 | # PI benchmarks
17 | hyperfine 'deno run testing/bench/pi.js' './elsa run testing/bench/pi.js' 'node testing/bench/pi.js' -s full -r 100 --warmup 50 --export-json ./benchmarks/pi.json -i
18 |
19 | test:
20 | go test ./testing
21 | ./elsa test --fs --net
22 |
23 | test-create-out:
24 | ./elsa bundle testing/bundle/local_imports.js >> testing/bundle/local_imports.js.out
25 | ./elsa bundle testing/bundle/hello.ts >> testing/bundle/hello.ts.out
26 | ./elsa bundle testing/bundle/basics.js >> testing/bundle/basics.js.out
27 |
28 | clean-cache:
29 | rm -rf /tmp/x.nest.land/
30 | rm -rf /tmp/deno.land/
31 |
32 | fmt:
33 | gofmt -w .
34 | prettier --write .
35 |
36 | check-fmt:
37 | gofmt -l .
38 | prettier --check .
39 |
40 | minify-typescript:
41 | terser --compress --mangle -o ./typescript/typescript.js -- ./typescript/typescript.js
42 |
43 | .PHONY: build benchmark clean-cache fmt check-fmt
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Elsa
2 |
3 | [![Discord invite][]][discord invite link]
4 |
5 |
6 |
7 | Elsa is a _minimal_ JavaScript and TypeScript runtime written in Go. Built on top of quickjs and heavily inspired by Deno.
8 |
9 | ### Features
10 |
11 | - URL imports.
12 | - useful Web APIs.
13 | - compiles TypeScript out of the box.
14 | - bundling. `elsa bundle`
15 | - compiling to native distributable binaries. `elsa compile`
16 |
17 | ```typescript
18 | // hello.ts
19 | import { hello } from "https://x.nest.land/arweave-hello@0.0.2/mod.ts";
20 |
21 | hello("Elsa");
22 | ```
23 |
24 | ```shell
25 | > elsa run hello.ts
26 | Hello, Elsa
27 | ```
28 |
29 | [build status - badge]: https://github.com/elsaland/elsa/workflows/Build/badge.svg
30 | [build status]: https://github.com/elsaland/elsa/actions
31 | [discord invite]: https://img.shields.io/discord/757562931725467709?color=697EC4&label=Discord&logo=discord&logoColor=FDFEFE&style=flat-square
32 | [discord invite link]: https://discord.gg/Dw534ZY
33 |
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elsaland/elsa/1982c3d1cad0a8ed4160b52d68c7c941445a8fa5/assets/logo.png
--------------------------------------------------------------------------------
/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
49 |
--------------------------------------------------------------------------------
/bootstrap/include.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | type ByNumericalFilename []os.FileInfo
10 |
11 | func (nf ByNumericalFilename) Len() int { return len(nf) }
12 | func (nf ByNumericalFilename) Swap(i, j int) { nf[i], nf[j] = nf[j], nf[i] }
13 | func (nf ByNumericalFilename) Less(i, j int) bool {
14 |
15 | // Use path names
16 | pathA := nf[i].Name()
17 | pathB := nf[j].Name()
18 |
19 | // Grab integer value of each filename by parsing the string and slicing off
20 | // the extension
21 | a, err1 := strconv.ParseInt(pathA[0:strings.LastIndex(pathA, ".")], 10, 64)
22 | b, err2 := strconv.ParseInt(pathB[0:strings.LastIndex(pathB, ".")], 10, 64)
23 |
24 | // If any were not numbers sort lexographically
25 | if err1 != nil || err2 != nil {
26 | return pathA < pathB
27 | }
28 |
29 | // Which integer is smaller?
30 | return a < b
31 | }
32 |
--------------------------------------------------------------------------------
/bootstrap/snap.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "log"
7 | "os"
8 | "os/exec"
9 | "path/filepath"
10 | "sort"
11 |
12 | "github.com/tdewolff/minify/v2"
13 | "github.com/tdewolff/minify/v2/js"
14 | )
15 |
16 | func main() {
17 | files, _ := ioutil.ReadDir("js")
18 |
19 | sort.Sort(ByNumericalFilename(files))
20 |
21 | m := minify.New()
22 | m.AddFunc("text/javascript", js.Minify)
23 |
24 | binCmd := []string{"run", "github.com/go-bindata/go-bindata/go-bindata", "-pkg", "core", "-o", "./core/data.go", "typescript/", "target/"}
25 | var finalSource string
26 | for _, f := range files {
27 | log.Printf("Bundling %s\n", f.Name())
28 |
29 | file, err := os.Open(filepath.Join("js", f.Name()))
30 | if err != nil {
31 | log.Fatalf("Got error opening %s: %v", f.Name(), err)
32 | }
33 |
34 | buf := new(bytes.Buffer)
35 | if err := m.Minify("text/javascript", buf, file); err != nil {
36 | log.Fatalf("Got error minifying %s: %v", f.Name(), err)
37 | }
38 |
39 | finalSource += buf.String() + "\n"
40 | }
41 | err := os.Mkdir("target", 0750)
42 | if err != nil {
43 | log.Fatalf("Error in making directory - %v", err)
44 | }
45 | err = ioutil.WriteFile(filepath.Join("target", "elsa.js"), []byte(finalSource), 0644)
46 | if err != nil {
47 | log.Fatalf("Error writing file %v", err)
48 | }
49 | cmd := exec.Command("go", binCmd...)
50 | log.Printf("Running command and waiting for it to finish...")
51 | err = cmd.Run()
52 | log.Printf("Command finished with error: %v", err)
53 | }
54 |
--------------------------------------------------------------------------------
/bundler/bundle.go:
--------------------------------------------------------------------------------
1 | package bundler
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 | "net/url"
8 | "os"
9 | "path"
10 | "path/filepath"
11 |
12 | "github.com/elsaland/elsa/module"
13 | "github.com/elsaland/elsa/util"
14 |
15 | "github.com/asaskevich/govalidator"
16 | "github.com/evanw/esbuild/pkg/api"
17 | )
18 |
19 | var cache = ElsaCache{os.TempDir()}
20 |
21 | func BundleModule(file string, minify bool, config *module.Config) string {
22 | tsconfig := ""
23 | dir := filepath.Dir(file)
24 | tsconfigPath := path.Join(dir, "tsconfig.json")
25 | if _, err := os.Stat(tsconfigPath); err == nil || !os.IsNotExist(err) {
26 | tsconfig = tsconfigPath
27 | }
28 | bundle := api.Build(api.BuildOptions{
29 | EntryPoints: []string{file},
30 | Outfile: "output.js",
31 | Bundle: true,
32 | Target: api.ESNext,
33 | LogLevel: api.LogLevelInfo,
34 | MinifyIdentifiers: minify,
35 | MinifySyntax: minify,
36 | MinifyWhitespace: minify,
37 | Tsconfig: tsconfig,
38 | Plugins: []func(api.Plugin){
39 |
40 | func(plugin api.Plugin) {
41 | plugin.SetName("url-loader")
42 | plugin.AddResolver(api.ResolverOptions{Filter: "^https?://"},
43 | func(args api.ResolverArgs) (api.ResolverResult, error) {
44 | possibleCachePath := cache.UrlToPath(args.Path)
45 | if cache.InCache(possibleCachePath) && cache.Exists(possibleCachePath) {
46 | return api.ResolverResult{Path: possibleCachePath, Namespace: ""}, nil
47 | }
48 | // Get the data
49 | f := BundleURL(args.Path, minify)
50 | return api.ResolverResult{Path: f, Namespace: ""}, nil
51 |
52 | })
53 | },
54 | },
55 | })
56 | if bundle.Errors != nil {
57 | os.Exit(1)
58 | }
59 | return string(bundle.OutputFiles[0].Contents)
60 | }
61 |
62 | func BundleURL(uri string, minify bool) string {
63 | resp, _ := http.Get(uri)
64 | fileName := cache.BuildFileName(uri)
65 | util.LogInfo("Downloading", fmt.Sprintf("%s => %s", uri, fileName))
66 | defer resp.Body.Close()
67 | file, err := cache.Create(fileName)
68 | if err != nil {
69 | util.LogError("Internal", fmt.Sprintf("%s", err))
70 | os.Exit(1)
71 | }
72 | io.Copy(file, resp.Body)
73 | defer file.Close()
74 | bundle := api.Build(api.BuildOptions{
75 | EntryPoints: []string{file.Name()},
76 | Outfile: "output.js",
77 | Bundle: true,
78 | Target: api.ES2015,
79 | LogLevel: api.LogLevelInfo,
80 | MinifyIdentifiers: minify,
81 | MinifyWhitespace: minify,
82 | MinifySyntax: minify,
83 | Plugins: []func(api.Plugin){
84 |
85 | func(plugin api.Plugin) {
86 | plugin.SetName("url-loader2")
87 | plugin.AddResolver(api.ResolverOptions{Filter: ".*?"},
88 | func(args api.ResolverArgs) (api.ResolverResult, error) {
89 | dir := filepath.Dir(file.Name())
90 | possibleCachePath := path.Join(dir, args.Path)
91 | if cache.InCache(possibleCachePath) && cache.Exists(possibleCachePath) {
92 | return api.ResolverResult{Path: possibleCachePath, Namespace: ""}, nil
93 | }
94 | if govalidator.IsURL(args.Path) {
95 | uri = args.Path
96 | cha := make(chan string)
97 | go func(url string, u chan string) {
98 | u <- BundleURL(url, minify)
99 | }(args.Path, cha)
100 | bundle := <-cha
101 | return api.ResolverResult{Path: bundle, Namespace: ""}, nil
102 | }
103 | base, err := url.Parse(uri)
104 | if err != nil {
105 | panic(err)
106 | }
107 | pth, err := url.Parse(args.Path)
108 | if err != nil {
109 | panic(err)
110 | }
111 | loc := base.ResolveReference(pth).String()
112 | fileName := cache.BuildFileName(loc)
113 | util.LogInfo("Downloading", fmt.Sprintf("%s => %s", loc, file.Name()))
114 | // Get the data
115 | resp, _ := http.Get(loc)
116 | defer resp.Body.Close()
117 | file, err := cache.Create(fileName)
118 | if err != nil {
119 | util.LogError("Internal", fmt.Sprintf("%s", err))
120 | os.Exit(1)
121 | }
122 | _, err = io.Copy(file, resp.Body)
123 | if err != nil {
124 | util.LogError("Internal", fmt.Sprintf("%s", err))
125 | os.Exit(1)
126 | }
127 |
128 | defer file.Close()
129 |
130 | return api.ResolverResult{Path: file.Name(), Namespace: ""}, nil
131 |
132 | })
133 | },
134 | },
135 | })
136 | if bundle.Errors != nil {
137 | os.Exit(1)
138 | }
139 | return file.Name()
140 | }
141 |
--------------------------------------------------------------------------------
/bundler/cache.go:
--------------------------------------------------------------------------------
1 | package bundler
2 |
3 | import (
4 | "net/url"
5 | "os"
6 | "path"
7 | "path/filepath"
8 | "strings"
9 | )
10 |
11 | type ElsaCache struct {
12 | dir string
13 | }
14 |
15 | func (cache *ElsaCache) BuildFileName(uri string) string {
16 | fileUrl, _ := url.Parse(uri)
17 | path := path.Join(cache.dir, fileUrl.Host, fileUrl.Path)
18 | return path
19 | }
20 |
21 | func (cache *ElsaCache) PathToUrl(path string) string {
22 | parts := strings.Split(path, "/")[2:]
23 | url, _ := url.Parse("https://" + strings.Join(parts, "/"))
24 | return url.String()
25 | }
26 |
27 | func (cache *ElsaCache) UrlToPath(url string) string {
28 | parts := strings.Split(url, "//")[1]
29 | path := path.Join(cache.dir, parts)
30 | return path
31 | }
32 |
33 | func (cache *ElsaCache) InCache(path string) bool {
34 | return strings.HasPrefix(path, cache.dir)
35 | }
36 |
37 | func (cache *ElsaCache) Exists(path string) bool {
38 | if _, err := os.Stat(path); os.IsNotExist(err) {
39 | return false
40 | }
41 | return true
42 | }
43 |
44 | func (cache *ElsaCache) Create(p string) (*os.File, error) {
45 | if err := os.MkdirAll(filepath.Dir(p), 0750); err != nil {
46 | return nil, err
47 | }
48 | return os.Create(p)
49 | }
50 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "log"
7 | "os"
8 | "path"
9 | "path/filepath"
10 | "runtime"
11 |
12 | "github.com/elsaland/elsa/core"
13 | "github.com/elsaland/elsa/core/options"
14 | "github.com/elsaland/elsa/module"
15 | "github.com/elsaland/elsa/packager"
16 | "github.com/elsaland/elsa/util"
17 | "github.com/fatih/color"
18 | "github.com/mitchellh/go-homedir"
19 | "github.com/spf13/cobra"
20 | )
21 |
22 | var homeDir, _ = homedir.Dir()
23 |
24 | var installSite = path.Join(homeDir, "./.elsa/")
25 |
26 | // Elsa functions expected to be passed into cmd
27 | type Elsa struct {
28 | Run func(opt options.Options)
29 | Dev func(og string, opt options.Options)
30 | Bundle func(file string, minify bool, config *module.Config) string
31 | }
32 |
33 | // Execute start the CLI
34 | func Execute(elsa Elsa) {
35 | // TODO: need to come to a concrete conclusion
36 | // Load mod.toml (if exists)
37 | config, err := module.ConfigLoad()
38 | util.Check(err)
39 |
40 | color.NoColor = config.Options.NoColor
41 |
42 | var fsFlag bool
43 | var netFlag bool
44 | var minifyFlag bool
45 | var envFlag bool
46 | var installName string
47 |
48 | // Root command
49 | var rootCmd = &cobra.Command{
50 | Use: "",
51 | Short: "",
52 | Run: func(cmd *cobra.Command, args []string) {
53 | core.Repl()
54 | },
55 | }
56 |
57 | // Run subcommand
58 | var runCmd = &cobra.Command{
59 | Use: "run [file]",
60 | Short: "Run a JavaScript and TypeScript source file",
61 | Args: cobra.MinimumNArgs(1),
62 | Run: func(cmd *cobra.Command, args []string) {
63 | if len(args) >= 0 {
64 | bundle := elsa.Bundle(args[0], true, config)
65 | env := options.Environment{
66 | NoColor: config.Options.NoColor,
67 | Args: args[1:],
68 | RunTests: false,
69 | }
70 | opt := options.Options{
71 | SourceFile: args[0],
72 | Source: bundle,
73 | Perms: &options.Perms{fsFlag, netFlag, envFlag},
74 | Env: env,
75 | }
76 | elsa.Run(opt)
77 | }
78 | },
79 | }
80 |
81 | // --fs,--net, --env flags
82 | runCmd.Flags().BoolVar(&fsFlag, "fs", false, "Allow file system access")
83 | runCmd.Flags().BoolVar(&netFlag, "net", false, "Allow net access")
84 | runCmd.Flags().BoolVar(&envFlag, "env", false, "Allow Environment Variables access")
85 |
86 | // dev subcommand to run script in development mode
87 | var devCmd = &cobra.Command{
88 | Use: "dev [file]",
89 | Short: "Run a script in development mode.",
90 | Long: `Run a script in development mode. It enables type-checking using the inbuilt TypeScript compiler.`,
91 | Args: cobra.MinimumNArgs(1),
92 | Run: func(cmd *cobra.Command, args []string) {
93 | if len(args) >= 0 {
94 | bundle := elsa.Bundle(args[0], true, config)
95 | env := options.Environment{
96 | NoColor: config.Options.NoColor,
97 | Args: args[1:],
98 | RunTests: false,
99 | }
100 | opt := options.Options{
101 | SourceFile: args[0],
102 | Source: bundle,
103 | Perms: &options.Perms{
104 | Fs: true,
105 | Env: true,
106 | Net: true,
107 | },
108 | Env: env,
109 | }
110 | og, _ := ioutil.ReadFile(args[0])
111 | elsa.Dev(string(og), opt)
112 | }
113 | },
114 | }
115 |
116 | // bundle subcommand to bundle a source file
117 | var bundleCmd = &cobra.Command{
118 | Use: "bundle [file]",
119 | Short: "Bundle your script to a single JavaScript file",
120 | Long: `Bundle your script to a single JavaScript file. It utilizes esbuild for super fast bundling.`,
121 | Args: cobra.MinimumNArgs(1),
122 | Run: func(cmd *cobra.Command, args []string) {
123 | if len(args) >= 0 {
124 | out := elsa.Bundle(args[0], minifyFlag, config)
125 | fmt.Println(out)
126 | }
127 | },
128 | }
129 |
130 | // --minify flag for bundling
131 | bundleCmd.Flags().BoolVarP(&minifyFlag, "minify", "m", false, "Minify the output bundle")
132 |
133 | // pkg subcommand for trigger the packager
134 | var pkgCmd = &cobra.Command{
135 | Use: "pkg [file]",
136 | Short: "Package your script to a standalone executable.",
137 | Long: `Package your script to a standalone executable.`,
138 | Args: cobra.MinimumNArgs(1),
139 | Run: func(cmd *cobra.Command, args []string) {
140 | if len(args) >= 0 {
141 | packager.PkgSource(args[0])
142 | }
143 | },
144 | }
145 |
146 | // test subcommand to run test files
147 | var testCmd = &cobra.Command{
148 | Use: "test",
149 | Short: "Run tests for your Elsa scripts.",
150 | Long: `Run tests for your Elsa scripts. All files matching *_test.js are run.`,
151 | Run: func(cmd *cobra.Command, args []string) {
152 | env := options.Environment{
153 | NoColor: config.Options.NoColor,
154 | Args: args,
155 | RunTests: true,
156 | }
157 | opt := options.Options{
158 | Perms: &options.Perms{fsFlag, netFlag, envFlag},
159 | Env: env,
160 | }
161 | tests := CollectTests()
162 | for _, test := range tests {
163 | opt.SourceFile = test
164 | bundle := elsa.Bundle(test, true, config)
165 | opt.Source = bundle
166 | elsa.Run(opt)
167 | }
168 | },
169 | }
170 |
171 | // --net, --fs, --env perms
172 | testCmd.Flags().BoolVar(&fsFlag, "fs", false, "Allow file system access")
173 | testCmd.Flags().BoolVar(&netFlag, "net", false, "Allow net access")
174 | testCmd.Flags().BoolVar(&envFlag, "env", false, "Allow Environment Variables access")
175 |
176 | // install subcommand to bundle and shebang to PATH env
177 | var installCmd = &cobra.Command{
178 | Use: "install",
179 | Short: "Install an Elsa module.",
180 | Long: `Install an Elsa module.`,
181 | Run: func(cmd *cobra.Command, args []string) {
182 | if len(args) >= 0 {
183 | out := elsa.Bundle(args[0], true, config)
184 | bundleLoc := path.Join(os.TempDir(), installName+".js")
185 | err := ioutil.WriteFile(bundleLoc, []byte(out), 0777)
186 | if err != nil {
187 | panic(err)
188 | }
189 | scriptFile := path.Join(installSite, installName)
190 |
191 | // add .cmd in script for windows
192 | isWindows(&scriptFile, scriptFile+".cmd")
193 | err = ioutil.WriteFile(scriptFile, []byte(shebang(bundleLoc)), 0777)
194 | if err != nil {
195 | panic(err)
196 | }
197 | fmt.Println("Installation complete.")
198 | }
199 | },
200 | }
201 | installCmd.Flags().StringVar(&installName, "name", "00", "Executable name of the installed script")
202 | // Add subcommands to root command
203 | rootCmd.AddCommand(bundleCmd, runCmd, pkgCmd, devCmd, testCmd, installCmd)
204 |
205 | // Execute! :)
206 | if err := rootCmd.Execute(); err != nil {
207 | fmt.Println(err)
208 | os.Exit(1)
209 | }
210 | }
211 |
212 | func shebang(loc string) string {
213 | exec := `
214 | #!/bin/sh
215 | elsa "run" "%s" "$@"`
216 | // for windows
217 | isWindows(&exec, `@elsa "run" "%s" %*`)
218 |
219 | return fmt.Sprintf(exec, loc)
220 | }
221 |
222 | // replace a string if it is windows
223 | func isWindows(toChange *string, replacement string) {
224 | if runtime.GOOS == "windows" {
225 | *toChange = replacement
226 | }
227 | }
228 |
229 | // match test files
230 | func matchedFiles(name string) bool {
231 | matchedJS, err := filepath.Match("*_test.js", name)
232 | matchedTS, err := filepath.Match("*_test.ts", name)
233 | matchedJSTest, err := filepath.Match("*.test.js", name)
234 | matchedTSTest, err := filepath.Match("*.test.ts", name)
235 |
236 | if err != nil {
237 | log.Fatal(err)
238 | }
239 |
240 | return (matchedJS || matchedTS || matchedTSTest || matchedJSTest)
241 | }
242 |
243 | // CollectTests files
244 | func CollectTests() []string {
245 | var testFiles []string
246 | e := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
247 | if err == nil {
248 | if err != nil {
249 | return nil
250 | }
251 | if matchedFiles(info.Name()) {
252 | testFiles = append(testFiles, path)
253 | }
254 | }
255 | return nil
256 | })
257 | if e != nil {
258 | log.Fatal(e)
259 | }
260 | return testFiles
261 | }
262 |
--------------------------------------------------------------------------------
/core/README.md:
--------------------------------------------------------------------------------
1 | ## `core`
2 |
3 | The directory contains code for Elsa's runtime agnostic.
4 | It comes with a simple Go API for developers to embed Elsa applications into their Go code.
5 |
6 | ### Usage
7 |
8 | ```go
9 | package main
10 |
11 | import (
12 | "github.com/elsaland/elsa/core"
13 | "github.com/elsaland/elsa/core/options"
14 | )
15 |
16 | var source string: = "console.log(1)"
17 |
18 | func main() {
19 | env: = options.Environment {
20 | NoColor: false, // Set true to disable coloured output
21 | Args: os.Args[1: ],
22 | }
23 | opt: = options.Options {
24 | SourceFile: "file.js",
25 | Source: source,
26 | Perms: & options.Perms {
27 | Fs: false, // Set true to allow file system access
28 | },
29 | Env: env,
30 | }
31 | core.Run(opt)
32 | }
33 | ```
34 |
--------------------------------------------------------------------------------
/core/console.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/elsaland/quickjs"
8 | "github.com/fatih/color"
9 | )
10 |
11 | // Create a new console formatter from console_util.go
12 | var f = NewFormatter()
13 |
14 | // ConsoleLog console.log bindings to quickjs engine
15 | func ConsoleLog(ctx *quickjs.Context, value []quickjs.Value) quickjs.Value {
16 | data := value[2]
17 | // dataType is the JavaScript type of the data => `typeof arg`
18 | dataType := value[1].String()
19 | var result interface{}
20 | switch dataType {
21 | case "string":
22 | // Prints a string (without color)
23 | fmt.Println(data.String())
24 | case "function":
25 | // Prints String(myFunction)
26 | fmt.Fprintln(color.Output, color.New(color.FgCyan).SprintFunc()(data.String()))
27 | case "bigint":
28 | // Prints bigint corresponding to number
29 | fmt.Fprintln(color.Output, color.New(color.FgYellow).SprintFunc()(data.BigInt()))
30 | case "number":
31 | // Prints a number
32 | fmt.Fprintln(color.Output, color.New(color.FgYellow).SprintFunc()(data.Int32()))
33 | default:
34 | // Hands over the data as string to console util for parsing arrays and objects
35 | json.Unmarshal([]byte(data.String()), &result)
36 | prty, _ := f.Marshal(result)
37 | // Prints the formatted result
38 | fmt.Fprintln(color.Output, string(prty))
39 | }
40 |
41 | return ctx.Null()
42 | }
43 |
--------------------------------------------------------------------------------
/core/console_test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/elsaland/elsa/util"
7 | "testing"
8 | )
9 |
10 | const testString = `"This is a string"`
11 | const testArray = `["string", 100, {}]`
12 | const testNumber = `120`
13 | const testDiverseJSON = `{
14 | "str": "foo",
15 | "num": 100,
16 | "bool": false,
17 | "null": null,
18 | "array": ["foo", "bar", "baz"],
19 | "obj": { "a": 1, "b": 2 }
20 | }`
21 |
22 | func expectPass(str string, t *testing.T) {
23 | var result interface{}
24 | err := json.Unmarshal([]byte(str), &result)
25 | util.Check(err)
26 | prty, err := Marshal(result)
27 | util.Check(err)
28 | fmt.Println(string(prty))
29 | }
30 |
31 | func TestString(t *testing.T) { expectPass(testString, t) }
32 | func TestDiverseJSON(t *testing.T) { expectPass(testDiverseJSON, t) }
33 | func TestNumber(t *testing.T) { expectPass(testNumber, t) }
34 | func TestArray(t *testing.T) { expectPass(testArray, t) }
35 |
--------------------------------------------------------------------------------
/core/console_util.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 |
6 | "bytes"
7 | "encoding/json"
8 | "sort"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/fatih/color"
13 | )
14 |
15 | const initialDepth = 0
16 | const valueSep = ","
17 | const null = "null"
18 | const startMap = "{"
19 | const endMap = "}"
20 | const startArray = "["
21 | const endArray = "]"
22 |
23 | const emptyMap = startMap + endMap
24 | const emptyArray = startArray + endArray
25 |
26 | type Formatter struct {
27 | KeyColor *color.Color
28 | StringColor *color.Color
29 | BoolColor *color.Color
30 | NumberColor *color.Color
31 | NullColor *color.Color
32 | StringMaxLength int
33 | Indent int
34 | DisabledColor bool
35 | RawStrings bool
36 | }
37 |
38 | func NewFormatter() *Formatter {
39 | return &Formatter{
40 | KeyColor: color.New(color.FgWhite),
41 | StringColor: color.New(color.FgGreen),
42 | BoolColor: color.New(color.FgYellow),
43 | NumberColor: color.New(color.FgCyan),
44 | NullColor: color.New(color.FgMagenta),
45 | StringMaxLength: 0,
46 | DisabledColor: false,
47 | Indent: 0,
48 | RawStrings: false,
49 | }
50 | }
51 |
52 | func (f *Formatter) sprintfColor(c *color.Color, format string, args ...interface{}) string {
53 | if f.DisabledColor || c == nil {
54 | return fmt.Sprintf(format, args...)
55 | }
56 | return c.SprintfFunc()(format, args...)
57 | }
58 |
59 | func (f *Formatter) sprintColor(c *color.Color, s string) string {
60 | if f.DisabledColor || c == nil {
61 | return fmt.Sprint(s)
62 | }
63 | return c.SprintFunc()(s)
64 | }
65 |
66 | func (f *Formatter) writeIndent(buf *bytes.Buffer, depth int) {
67 | buf.WriteString(strings.Repeat(" ", f.Indent*depth))
68 | }
69 |
70 | func (f *Formatter) writeObjSep(buf *bytes.Buffer) {
71 | if f.Indent != 0 {
72 | buf.WriteByte('\n')
73 | } else {
74 | buf.WriteByte(' ')
75 | }
76 | }
77 |
78 | func (f *Formatter) Marshal(jsonObj interface{}) ([]byte, error) {
79 | buffer := bytes.Buffer{}
80 | f.marshalValue(jsonObj, &buffer, initialDepth)
81 | return buffer.Bytes(), nil
82 | }
83 |
84 | func (f *Formatter) marshalMap(m map[string]interface{}, buf *bytes.Buffer, depth int) {
85 | remaining := len(m)
86 |
87 | if remaining == 0 {
88 | buf.WriteString(emptyMap)
89 | return
90 | }
91 |
92 | keys := make([]string, 0)
93 | for key := range m {
94 | keys = append(keys, key)
95 | }
96 |
97 | sort.Strings(keys)
98 |
99 | buf.WriteString(startMap)
100 | f.writeObjSep(buf)
101 |
102 | for _, key := range keys {
103 | f.writeIndent(buf, depth+1)
104 | buf.WriteString(f.KeyColor.Sprintf("\"%s\": ", key))
105 | f.marshalValue(m[key], buf, depth+1)
106 | remaining--
107 | if remaining != 0 {
108 | buf.WriteString(valueSep)
109 | }
110 | f.writeObjSep(buf)
111 | }
112 | f.writeIndent(buf, depth)
113 | buf.WriteString(endMap)
114 | }
115 |
116 | func (f *Formatter) marshalArray(a []interface{}, buf *bytes.Buffer, depth int) {
117 | if len(a) == 0 {
118 | buf.WriteString(emptyArray)
119 | return
120 | }
121 |
122 | buf.WriteString(startArray)
123 | f.writeObjSep(buf)
124 |
125 | for i, v := range a {
126 | f.writeIndent(buf, depth+1)
127 | f.marshalValue(v, buf, depth+1)
128 | if i < len(a)-1 {
129 | buf.WriteString(valueSep)
130 | }
131 | f.writeObjSep(buf)
132 | }
133 | f.writeIndent(buf, depth)
134 | buf.WriteString(endArray)
135 | }
136 |
137 | func (f *Formatter) marshalValue(val interface{}, buf *bytes.Buffer, depth int) {
138 | switch v := val.(type) {
139 | case map[string]interface{}:
140 | f.marshalMap(v, buf, depth)
141 | case []interface{}:
142 | f.marshalArray(v, buf, depth)
143 | case string:
144 | f.marshalString(v, buf)
145 | case float64:
146 | buf.WriteString(f.sprintColor(f.NumberColor, strconv.FormatFloat(v, 'f', -1, 64)))
147 | case bool:
148 | buf.WriteString(f.sprintColor(f.BoolColor, (strconv.FormatBool(v))))
149 | case nil:
150 | buf.WriteString(f.sprintColor(f.NullColor, null))
151 | case json.Number:
152 | buf.WriteString(f.sprintColor(f.NumberColor, v.String()))
153 | }
154 | }
155 |
156 | func (f *Formatter) marshalString(str string, buf *bytes.Buffer) {
157 | if !f.RawStrings {
158 | strBytes, _ := json.Marshal(str)
159 | str = string(strBytes)
160 | }
161 |
162 | if f.StringMaxLength != 0 && len(str) >= f.StringMaxLength {
163 | str = fmt.Sprintf("%s...", str[0:f.StringMaxLength])
164 | }
165 |
166 | buf.WriteString(f.sprintColor(f.StringColor, str))
167 | }
168 |
169 | // Marshal JSON data with default options
170 | func Marshal(jsonObj interface{}) ([]byte, error) {
171 | return NewFormatter().Marshal(jsonObj)
172 | }
173 |
--------------------------------------------------------------------------------
/core/dispatch.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/elsaland/elsa/util"
8 |
9 | "github.com/elsaland/elsa/core/ops"
10 | "github.com/elsaland/elsa/core/options"
11 | "github.com/elsaland/quickjs"
12 | "github.com/spf13/afero"
13 | )
14 |
15 | // ElsaSendNS Native function corresponding to the JavaScript global `__send`
16 | // It is binded with `__send` and accepts arguments including op ID
17 | func ElsaSendNS(elsa *options.Elsa) func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value {
18 | // Create a new file system driver
19 | var fs = ops.FsDriver{
20 | // NOTE: afero can also be used to create in-memory file system
21 | // it can be a feature to provide in the future
22 | Fs: afero.NewOsFs(),
23 | Perms: elsa.Perms,
24 | }
25 | // The returned function handles the op and execute corresponding native code
26 | return func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value {
27 | switch args[0].Int32() {
28 | case FSRead:
29 | CheckFs(elsa.Perms)
30 | file := args[1]
31 | val := fs.ReadFile(ctx, file)
32 | return val
33 | case FSExists:
34 | CheckFs(elsa.Perms)
35 | file := args[1]
36 | val := fs.Exists(ctx, file)
37 | return val
38 | case FSWrite:
39 | CheckFs(elsa.Perms)
40 | file := args[1]
41 | contents := args[2]
42 | val := fs.WriteFile(ctx, file, contents)
43 | return val
44 | case FSCwd:
45 | CheckFs(elsa.Perms)
46 | val := fs.Cwd(ctx)
47 | return val
48 | case FSStat:
49 | CheckFs(elsa.Perms)
50 | file := args[1]
51 | val := fs.Stat(ctx, file)
52 | return val
53 | case FSRemove:
54 | CheckFs(elsa.Perms)
55 | file := args[1]
56 | val := fs.Remove(ctx, file)
57 | return val
58 | case Log:
59 | return ConsoleLog(ctx, args)
60 | case Plugin:
61 | plugin := args[1].String()
62 | input := args[2].String()
63 | dat := (OpenPlugin(plugin, input)).(string)
64 | val := ctx.String(dat)
65 | defer val.Free()
66 | return val
67 | case Fetch:
68 | CheckNet(elsa.Perms)
69 | one := args[1]
70 | url := args[2]
71 | body := ops.Fetch(ctx, url)
72 | obj := ctx.Object()
73 | defer obj.Free()
74 | obj.Set("ok", body)
75 | elsa.Recv(one, obj)
76 | return ctx.Null()
77 | case Serve:
78 | id := args[1]
79 | url := args[2]
80 | cb := func(res quickjs.Value) string {
81 | obj := ctx.Object()
82 | defer obj.Free()
83 | obj.Set("ok", res)
84 | rtrn := elsa.Recv(id, res)
85 | return rtrn.String()
86 | }
87 | ops.Serve(ctx, cb, id, url)
88 | return ctx.Null()
89 | case FSMkdir:
90 | CheckFs(elsa.Perms)
91 | file := args[1]
92 | val := fs.Mkdir(ctx, file)
93 | return val
94 | case Env:
95 | CheckEnv(elsa.Perms)
96 | val := ops.Env(ctx, args)
97 | return val
98 | case FSWalk:
99 | CheckFs(elsa.Perms)
100 | file := args[1]
101 | val := fs.Walk(ctx, file)
102 | return val
103 | default:
104 | return ctx.Null()
105 | }
106 | }
107 | }
108 |
109 | // CheckFs utility to check whether file system access is avaliable or not
110 | func CheckFs(perms *options.Perms) {
111 | if !perms.Fs {
112 | util.LogError("Perms Error: ", "Filesystem access is blocked.")
113 | os.Exit(1)
114 | }
115 | }
116 |
117 | // CheckNet utility to check whether net access is avaliable or not
118 | func CheckNet(perms *options.Perms) {
119 | if !perms.Net {
120 | util.LogError("Perms Error: ", "Net is blocked.")
121 | os.Exit(1)
122 | }
123 | }
124 |
125 | func CheckEnv(perms *options.Perms) {
126 | if !perms.Env {
127 | util.LogError("Perms Error: ", "Environment Variables is blocked.")
128 | os.Exit(1)
129 | }
130 | }
131 |
132 | // ElsaRecvNS Native function corresponding to the JavaScript global `__recv`
133 | // It is binded with `__recv` and accepts arguments including recv ID of the async function
134 | func ElsaRecvNS(elsa *options.Elsa) func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value {
135 | // the returned function handles the __recv behaviour
136 | // It is capable of calling the callback for a particular async op after it has finished
137 | return func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value {
138 | fn := args[0]
139 | if elsa.Recv != nil {
140 | ctx.ThrowError(fmt.Errorf("recv cannot be called more than once"))
141 | return ctx.Null()
142 | }
143 | elsa.Recv = func(id quickjs.Value, val quickjs.Value) quickjs.Value {
144 | result := fn.Call(id, val)
145 | // defer result.Free()
146 | return result
147 | }
148 | return ctx.Null()
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/core/ops.go:
--------------------------------------------------------------------------------
1 | // Package core lists all the OPS in elsa
2 | // Will be updated as an when new OPS are added
3 | package core
4 |
5 | // FileSystem ops
6 | const (
7 | FSWrite = 1
8 | FSRead = 2
9 | FSExists = 3
10 | FSDirExists = 4
11 | FSCwd = 5
12 | Serve = 25
13 | FSStat = 6
14 | FSRemove = 7
15 | FSMkdir = 9
16 | FSWalk = 14
17 | )
18 |
19 | // console binding ops
20 | const (
21 | Log = 10
22 | )
23 |
24 | // plugin ops
25 | const (
26 | Plugin = 15
27 | )
28 |
29 | // fetch ops
30 | const (
31 | Fetch = 20
32 | )
33 |
34 | // env ops
35 | const (
36 | Env = 11
37 | )
38 |
--------------------------------------------------------------------------------
/core/ops/env.go:
--------------------------------------------------------------------------------
1 | package ops
2 |
3 | import (
4 | "encoding/json"
5 | "os"
6 | "strings"
7 |
8 | "github.com/elsaland/elsa/util"
9 | "github.com/elsaland/quickjs"
10 | )
11 |
12 | // Env elsa handler
13 | func Env(ctx *quickjs.Context, data []quickjs.Value) quickjs.Value {
14 |
15 | // Elsa.env.get
16 | if len(data) == 2 && data[1].IsString() {
17 | key := os.Getenv(data[1].String())
18 | return ctx.String(key)
19 | }
20 |
21 | // Elsa.env.set
22 | if len(data) == 3 && (data[1].IsString() && data[2].IsString()) {
23 | err := os.Setenv(data[1].String(), data[2].String())
24 | util.Check(err)
25 |
26 | return ctx.Null()
27 | }
28 |
29 | // Elsa.env.toObject
30 | if len(data) == 2 && data[1].IsBool() {
31 | getenvironment := func(envs []string, getkeyval func(item string) (key, val string)) map[string]string {
32 | items := make(map[string]string)
33 | for _, item := range envs {
34 | key, val := getkeyval(item)
35 | items[key] = val
36 | }
37 | return items
38 | }
39 |
40 | // get envs map
41 | environment := getenvironment(os.Environ(), func(item string) (key, val string) {
42 | splits := strings.Split(item, "=")
43 | key = splits[0]
44 | val = splits[1]
45 | return
46 | })
47 |
48 | jsonEnv, err := json.Marshal(environment)
49 |
50 | util.Check(err)
51 | // convert to json string object
52 | return ctx.String(string(jsonEnv))
53 | }
54 |
55 | return ctx.Null()
56 | }
57 |
--------------------------------------------------------------------------------
/core/ops/fetch.go:
--------------------------------------------------------------------------------
1 | package ops
2 |
3 | import (
4 | "github.com/elsaland/elsa/util"
5 |
6 | "github.com/elsaland/quickjs"
7 | "github.com/imroc/req"
8 | )
9 |
10 | func Fetch(ctx *quickjs.Context, url quickjs.Value) quickjs.Value {
11 | r, err := req.Get(url.String())
12 | util.Check(err)
13 | resp, err := r.ToString()
14 | util.Check(err)
15 | return ctx.String(resp)
16 | }
17 |
--------------------------------------------------------------------------------
/core/ops/fs.go:
--------------------------------------------------------------------------------
1 | package ops
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "os"
7 | "path/filepath"
8 | "time"
9 |
10 | "github.com/elsaland/elsa/core/options"
11 | "github.com/elsaland/elsa/util"
12 |
13 | "github.com/elsaland/quickjs"
14 | "github.com/spf13/afero"
15 | )
16 |
17 | type FsDriver struct {
18 | Perms *options.Perms
19 | Fs afero.Fs
20 | }
21 |
22 | var _ io.Reader = (*os.File)(nil)
23 |
24 | func (fs *FsDriver) ReadFile(ctx *quickjs.Context, path quickjs.Value) quickjs.Value {
25 | data, err := afero.ReadFile(fs.Fs, path.String())
26 | util.Check(err)
27 | return ctx.String(string(data))
28 | }
29 |
30 | func (fs *FsDriver) WriteFile(ctx *quickjs.Context, path quickjs.Value, content quickjs.Value) quickjs.Value {
31 | err := afero.WriteFile(fs.Fs, path.String(), []byte(content.String()), 0777)
32 | util.Check(err)
33 | return ctx.Bool(true)
34 | }
35 |
36 | func (fs *FsDriver) Exists(ctx *quickjs.Context, path quickjs.Value) quickjs.Value {
37 | data, err := afero.Exists(fs.Fs, path.String())
38 | util.Check(err)
39 | return ctx.Bool(data)
40 | }
41 |
42 | func (fs *FsDriver) DirExists(ctx *quickjs.Context, path quickjs.Value) quickjs.Value {
43 | data, err := afero.DirExists(fs.Fs, path.String())
44 | util.Check(err)
45 | return ctx.Bool(data)
46 | }
47 |
48 | func (fs *FsDriver) Cwd(ctx *quickjs.Context) quickjs.Value {
49 | dir, err := os.Getwd()
50 | util.Check(err)
51 | return ctx.String(dir)
52 | }
53 |
54 | type FileInfo struct {
55 | Name string
56 | Size int64
57 | Mode os.FileMode
58 | ModTime time.Time
59 | IsDir bool
60 | }
61 |
62 | func (fs *FsDriver) Stat(ctx *quickjs.Context, path quickjs.Value) quickjs.Value {
63 | entry, err := fs.Fs.Stat(path.String())
64 | util.Check(err)
65 | f := FileInfo{
66 | Name: entry.Name(),
67 | Size: entry.Size(),
68 | Mode: entry.Mode(),
69 | ModTime: entry.ModTime(),
70 | IsDir: entry.IsDir(),
71 | }
72 | output, err := json.Marshal(f)
73 | util.Check(err)
74 | return ctx.String(string(output))
75 | }
76 |
77 | func (fs *FsDriver) Remove(ctx *quickjs.Context, path quickjs.Value) quickjs.Value {
78 | err := fs.Fs.Remove(path.String())
79 | util.Check(err)
80 | return ctx.Bool(true)
81 | }
82 |
83 | func (fs *FsDriver) Mkdir(ctx *quickjs.Context, path quickjs.Value) quickjs.Value {
84 | err := fs.Fs.Mkdir(path.String(), os.FileMode(0777))
85 | util.Check(err)
86 | return ctx.Bool(true)
87 | }
88 |
89 | type walkFs struct {
90 | Name string
91 | Size int64
92 | Mode os.FileMode
93 | ModTime time.Time
94 | IsDir bool
95 | Path string
96 | }
97 |
98 | func (fs *FsDriver) Walk(ctx *quickjs.Context, pathDir quickjs.Value) quickjs.Value {
99 |
100 | var files []walkFs
101 |
102 | err := filepath.Walk(pathDir.String(), func(path string, info os.FileInfo, err error) error {
103 |
104 | data := walkFs{
105 | Name: info.Name(),
106 | Size: info.Size(),
107 | Mode: info.Mode(),
108 | ModTime: info.ModTime(),
109 | IsDir: info.IsDir(),
110 | Path: path,
111 | }
112 |
113 | if err != nil {
114 | util.Check(err)
115 | }
116 |
117 | files = append(files, data)
118 |
119 | return nil
120 | })
121 |
122 | if err != nil {
123 | util.Check(err)
124 | }
125 |
126 | output, err := json.Marshal(files)
127 | util.Check(err)
128 | return ctx.String(string(output))
129 | }
130 |
--------------------------------------------------------------------------------
/core/ops/serve.go:
--------------------------------------------------------------------------------
1 | package ops
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "net"
7 | "net/url"
8 |
9 | "github.com/elsaland/quickjs"
10 | "github.com/valyala/fasthttp"
11 | )
12 |
13 | // Serve listens for HTTP requests to host and calls callback sequentially on
14 | // every request.
15 | func Serve(ctx *quickjs.Context, callback func(request quickjs.Value) (response string),
16 | id quickjs.Value, host quickjs.Value) {
17 | var (
18 | reqs = make(chan *fasthttp.RequestCtx)
19 | resps = make(chan Response)
20 | errch = make(chan error, 1)
21 | )
22 | go func() {
23 | httpHandler := func(ctx *fasthttp.RequestCtx) {
24 | reqs <- ctx
25 | resp := <-resps
26 | ctx.SetStatusCode(int(resp.Status))
27 | _, err := ctx.Write([]byte(resp.Body))
28 | if err != nil {
29 | log.Fatalf("%v", err)
30 | }
31 | }
32 | errch <- fasthttp.ListenAndServe(host.String(), httpHandler)
33 | close(reqs)
34 | }()
35 | for {
36 | select {
37 | case <-errch:
38 | // TODO: throw the error as an exception to the JS script
39 | // see https://github.com/elsaland/elsa/issues/75
40 | break
41 | case req := <-reqs:
42 | reqjson, _ := json.Marshal(Request{
43 | Method: string(req.Method()),
44 | URL: req.URI(),
45 | Header: req.Request.Header,
46 | Path: string(req.Path()),
47 | Host: string(req.Host()),
48 | QueryArgs: req.QueryArgs(),
49 | PostArgs: req.PostArgs(),
50 | PostForm: req.PostArgs(),
51 | RemoteAddr: req.RemoteAddr(),
52 | RequestURI: string(req.RequestURI()),
53 | Referer: string(req.Referer()),
54 | UserAgent: string(req.UserAgent()),
55 | LocalAddr: req.LocalAddr(),
56 | RemoteIP: req.RemoteIP(),
57 | LocalIP: req.LocalIP(),
58 | })
59 | respjson := callback(ctx.String(string(reqjson)))
60 | var resp Response
61 | err := json.Unmarshal([]byte(respjson), &resp)
62 | if err != nil {
63 | log.Fatal(err)
64 | }
65 | resps <- resp
66 | }
67 | }
68 | }
69 |
70 | // Response response returned by callback from js
71 | type Response struct {
72 | // Status code
73 | Status int32
74 | // Body of the response
75 | Body string
76 | }
77 |
78 | type Request struct {
79 | Method string
80 | URL *fasthttp.URI
81 | Path string
82 | Header fasthttp.RequestHeader
83 | ContentLength int64
84 | TransferEncoding []string
85 | Host string
86 | Form url.Values
87 | PostForm *fasthttp.Args
88 | RemoteAddr net.Addr
89 | RequestURI string
90 | Referer string
91 | UserAgent string
92 | LocalAddr net.Addr
93 | RemoteIP net.IP
94 | LocalIP net.IP
95 | PostArgs *fasthttp.Args
96 | QueryArgs *fasthttp.Args
97 | }
98 |
--------------------------------------------------------------------------------
/core/options/options.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "github.com/elsaland/quickjs"
5 | )
6 |
7 | // Recv callback for an async op
8 | type Recv func(id quickjs.Value, val quickjs.Value) quickjs.Value
9 |
10 | // Elsa represents general data for the runtime
11 | type Elsa struct {
12 | // Permissions
13 | Perms *Perms
14 | // Async recv function
15 | Recv Recv
16 | }
17 |
18 | // Environment configure the runtime environment
19 | type Environment struct {
20 | // Enable or disable color logging
21 | NoColor bool
22 | // Command-line args to pass into Elsa.args
23 | Args []string
24 | // Whether to run tests associated with `Elsa.tests()`
25 | RunTests bool
26 | }
27 |
28 | // Perms permissions available for Elsa
29 | type Perms struct {
30 | // File system access
31 | Fs bool
32 | // Net access
33 | Net bool
34 | // Env access
35 | Env bool
36 | }
37 |
38 | // Options options for dispatching a new Elsa + QuickJS runtime
39 | type Options struct {
40 | // File name of the source (used for debuging purposes)
41 | SourceFile string
42 | // Source code
43 | Source string
44 | // Permission
45 | Perms *Perms
46 | // Configure Environment
47 | Env Environment
48 | }
49 |
--------------------------------------------------------------------------------
/core/plugins.go:
--------------------------------------------------------------------------------
1 | //go:build !windows
2 | // +build !windows
3 |
4 | package core
5 |
6 | import "plugin"
7 |
8 | // OpenPlugin open a dynamic lib and call the exported ElsaPlugin function
9 | // with the args provided by the plugin op
10 | // Currently, not compatible with windows
11 | func OpenPlugin(path string, arg interface{}) interface{} {
12 | // open the plugin
13 | p, err := plugin.Open(path)
14 | if err != nil {
15 | panic(err)
16 | }
17 | // lookup for exported variable and assign the argument
18 | v, err := p.Lookup("V")
19 | if err != nil {
20 | panic(err)
21 | }
22 | *v.(*interface{}) = arg
23 | // lookup for ElsaPlugin export and call the function
24 | f, err := p.Lookup("ElsaPlugin")
25 | if err != nil {
26 | panic(err)
27 | }
28 | return f.(func() interface{})()
29 | }
30 |
--------------------------------------------------------------------------------
/core/plugins_win.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package core
5 |
6 | import (
7 | "os"
8 |
9 | "github.com/elsaland/elsa/util"
10 | )
11 |
12 | // OpenPlugin Go plugins have not yet been implemented on windows
13 | func OpenPlugin(path string, arg interface{}) interface{} {
14 | util.LogError("Not supported", "Go plugins are not supported for windows. See https://github.com/golang/go/issues/19282")
15 | os.Exit(1)
16 | return nil
17 | }
18 |
--------------------------------------------------------------------------------
/core/repl.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "bufio"
5 | "errors"
6 | "fmt"
7 | "os"
8 |
9 | "github.com/elsaland/quickjs"
10 | )
11 |
12 | // Repl implementation
13 | func Repl() {
14 | stringToEval := ""
15 | fmt.Println("Elsa REPL")
16 | fmt.Println("exit using ctrl+c or close()")
17 |
18 | for true {
19 | fmt.Print("> ")
20 | reader := bufio.NewReader(os.Stdin)
21 | text, _ := reader.ReadString('\n')
22 |
23 | fmt.Println(Eval(text, &stringToEval))
24 | stringToEval += ";undefined;"
25 | }
26 | }
27 |
28 | // Eval js from string
29 | func Eval(text string, buffer *string) string {
30 | // repl close function
31 | closeEval := func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value {
32 | os.Exit(1)
33 | return ctx.Null()
34 | }
35 |
36 | evalRuntime := quickjs.NewRuntime()
37 | defer evalRuntime.Free()
38 |
39 | evalContext := evalRuntime.NewContext()
40 | defer evalContext.Free()
41 |
42 | //TODO(buttercubz) set globals functions
43 | globalsEval := evalContext.Globals()
44 |
45 | globalsEval.Set("close", evalContext.Function(closeEval))
46 |
47 | result, err := evalContext.Eval(*buffer + text)
48 | saveBuffer := check(err)
49 |
50 | if saveBuffer {
51 | *buffer += fmt.Sprintf(";undefined; %s", text)
52 | }
53 |
54 | defer result.Free()
55 |
56 | return result.String()
57 | }
58 |
59 | // check errors without exit
60 | func check(err error) bool {
61 | if err != nil {
62 | var evalErr *quickjs.Error
63 | if errors.As(err, &evalErr) {
64 | fmt.Println(evalErr.Cause)
65 | fmt.Println(evalErr.Stack)
66 | return false
67 | }
68 | }
69 | return true
70 | }
71 |
--------------------------------------------------------------------------------
/core/run.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/elsaland/elsa/core/options"
5 | "github.com/elsaland/elsa/util"
6 | "io"
7 |
8 | "github.com/elsaland/quickjs"
9 | )
10 |
11 | // PrepareRuntimeContext prepare the runtime and context with Elsa's internal ops
12 | // injects `__send` and `__recv` global dispatch functions into runtime
13 | func PrepareRuntimeContext(cxt *quickjs.Context, jsruntime quickjs.Runtime, args []string, flags *options.Perms, mode string) {
14 | // Assign perms
15 | elsa := &options.Elsa{Perms: flags}
16 |
17 | globals := cxt.Globals()
18 | // Attach send & recv global ops
19 | globals.SetFunction("__send", ElsaSendNS(elsa))
20 | globals.SetFunction("__recv", ElsaRecvNS(elsa))
21 |
22 | // Prepare runtime context with namespace and client op code
23 | // The snapshot is generated at bootstrap process
24 | snap, _ := Asset("target/elsa.js")
25 | k, err := cxt.Eval(string(snap))
26 | util.Check(err)
27 | defer k.Free()
28 |
29 | ns := globals.Get("Elsa")
30 | defer ns.Free()
31 | // Assign `Elsa.args` with the os args
32 | _Args := cxt.Array()
33 | for i, arg := range args {
34 | _Arg := cxt.String(arg)
35 | _Args.SetByUint32(uint32(i), _Arg)
36 | }
37 | ns.Set("args", _Args)
38 | // Assing `Elsa.mode` with current environment mode
39 | _Mode := cxt.String(mode)
40 | ns.Set("mode", _Mode)
41 | // Runtime check to execute async jobs
42 | for {
43 | _, err = jsruntime.ExecutePendingJob()
44 | if err == io.EOF {
45 | err = nil
46 | break
47 | }
48 | util.Check(err)
49 | }
50 | }
51 |
52 | // Run create and dispatch a QuickJS runtime binded with Elsa's OPs configurable using options
53 | func Run(opt options.Options) {
54 | // Create a new quickJS runtime
55 | jsruntime := quickjs.NewRuntime()
56 | defer jsruntime.Free()
57 |
58 | // Create a new runtime context
59 | cxt := jsruntime.NewContext()
60 | defer cxt.Free()
61 |
62 | // mode is not configurable directly and is to be determined based on RunTests
63 | // defaults to `run`
64 | mode := "run"
65 | if opt.Env.RunTests {
66 | mode = "test"
67 | }
68 |
69 | // Prepare runtime and context with Elsa namespace
70 | PrepareRuntimeContext(cxt, jsruntime, opt.Env.Args, opt.Perms, mode)
71 |
72 | // Evaluate the source
73 | result, err := cxt.EvalFile(opt.Source, opt.SourceFile)
74 | util.Check(err)
75 | defer result.Free()
76 |
77 | // Check for exceptions
78 | if result.IsException() {
79 | err = cxt.Exception()
80 | util.Check(err)
81 | }
82 |
83 | for {
84 | _, err = jsruntime.ExecutePendingJob()
85 | if err == io.EOF {
86 | err = nil
87 | break
88 | }
89 | util.Check(err)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/dev/dev.go:
--------------------------------------------------------------------------------
1 | package dev
2 |
3 | import (
4 | "github.com/elsaland/elsa/core"
5 | "github.com/elsaland/elsa/core/options"
6 | "github.com/elsaland/quickjs"
7 | )
8 |
9 | // AllowAll allow all flags when in development mode
10 | var AllowAll = options.Perms{
11 | // Allow file system access
12 | Fs: true,
13 | // Allow net access
14 | Net: true,
15 | }
16 |
17 | // TypeCheck run typechecking and report the diagnostics
18 | func TypeCheck(source string, sourceFile string, args []string) {
19 | // Callback function for reporting diagnostics to the user
20 | report := func(val quickjs.Value) {
21 | ReportDiagnostics(val)
22 | }
23 | // Trigger the compiler with the report callback and source
24 | // allow all perms and specify os args
25 | Compile(source, sourceFile, report, &AllowAll, args)
26 | }
27 |
28 | // RunDev invoke typechecking and execute
29 | func RunDev(og string, opt options.Options) {
30 | // Allow all perms when running in development mode
31 | opt.Perms = &AllowAll
32 | // Run typechecking
33 | TypeCheck(og, opt.SourceFile, opt.Env.Args)
34 | // Execute bundled script into a quickJS runtime
35 | core.Run(opt)
36 | }
37 |
--------------------------------------------------------------------------------
/dev/report.go:
--------------------------------------------------------------------------------
1 | package dev
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/elsaland/quickjs"
7 | )
8 |
9 | // ReportDiagnostics report TypeScript diagnostics
10 | func ReportDiagnostics(diagnostics quickjs.Value) {
11 | diag := diagnostics.String()
12 | if diag != "" {
13 | fmt.Println(diagnostics.String())
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/dev/typescript.go:
--------------------------------------------------------------------------------
1 | package dev
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 |
7 | "github.com/elsaland/elsa/core/options"
8 | "github.com/elsaland/elsa/util"
9 |
10 | "github.com/elsaland/elsa/core"
11 | "github.com/elsaland/quickjs"
12 | )
13 |
14 | func Compile(source string, sourceFile string, fn func(val quickjs.Value), flags *options.Perms, args []string) {
15 | data, err := core.Asset("typescript/typescript.js")
16 | if err != nil {
17 | panic("Asset was not found.")
18 | }
19 |
20 | runtime.LockOSThread()
21 | jsruntime := quickjs.NewRuntime()
22 | defer jsruntime.Free()
23 |
24 | context := jsruntime.NewContext()
25 | defer context.Free()
26 |
27 | core.PrepareRuntimeContext(context, jsruntime, args, flags, "dev")
28 |
29 | globals := context.Globals()
30 | report := func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value {
31 | fn(args[0])
32 | return ctx.Null()
33 | }
34 | d := func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value {
35 | asset, er := core.Asset(args[0].String())
36 | if er != nil {
37 | panic("Asset was not found.")
38 | }
39 | return ctx.String(string(asset))
40 | }
41 | globals.Set("Report", context.Function(report))
42 | globals.Set("Asset", context.Function(d))
43 | bundle := string(data) + jsCheck(source, sourceFile)
44 | result, err := context.Eval(bundle)
45 | util.Check(err)
46 | defer result.Free()
47 | }
48 |
49 | func jsCheck(source, sourceFile string) string {
50 | return fmt.Sprintf("typeCheck(`%s`, `%s`);", sourceFile, source)
51 | }
52 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/elsaland/elsa
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/Netflix/go-env v0.0.0-20200908232752-3e802f601e28
7 | github.com/andybalholm/brotli v1.0.1 // indirect
8 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
9 | github.com/elsaland/quickjs v0.0.0-20200926030524-a9835b20920b
10 | github.com/evanw/esbuild v0.7.3-0.20200919185132-ef34da4ee06e
11 | github.com/fatih/color v1.9.0
12 | github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd
13 | github.com/go-bindata/go-bindata v3.1.2+incompatible
14 | github.com/imroc/req v0.3.0
15 | github.com/klauspost/compress v1.11.1 // indirect
16 | github.com/lithdew/quickjs v0.0.0-20200714182134-aaa42285c9d2
17 | github.com/mitchellh/go-homedir v1.1.0
18 | github.com/pelletier/go-toml v1.2.0
19 | github.com/spf13/afero v1.4.0
20 | github.com/spf13/cobra v1.0.0
21 | github.com/stretchr/testify v1.6.1
22 | github.com/tdewolff/minify/v2 v2.9.5
23 | github.com/valyala/fasthttp v1.16.0
24 | golang.org/x/sys v0.2.0 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/js/00_core.js:
--------------------------------------------------------------------------------
1 | // Define op codes
2 | const __ops = {
3 | FSWrite: 1,
4 | FSRead: 2,
5 | FSExists: 3,
6 | FSDirExists: 4,
7 | FSCwd: 5,
8 | FSStat: 6,
9 | Serve: 25,
10 | FSRemove: 7,
11 | Fetch: 20,
12 | Log: 10,
13 | Plugin: 15,
14 | FSMkdir: 9,
15 | Env: 11,
16 | Walk: 14,
17 | };
18 |
19 | ((window) => {
20 | let initialized = false;
21 | let promiseTable = {};
22 | let promiseNextId = 1;
23 | function init() {
24 | if (initialized) return;
25 | initialized = true;
26 | globalThis.__recv(__recvAsync);
27 | }
28 |
29 | function __recvAsync(id, val) {
30 | if (!id) return;
31 | return promiseTable[id].resolve(val);
32 | }
33 |
34 | async function __sendAsync(op, cb, ...args) {
35 | init();
36 | const id = promiseNextId++;
37 | if (typeof cb == "function") {
38 | promiseTable[id] = { resolve: cb };
39 | globalThis.__send(op, ...[id, ...args]);
40 | } else {
41 | let resolve, reject;
42 | const promise = new Promise((resolve_, reject_) => {
43 | resolve = resolve_;
44 | reject = reject_;
45 | });
46 | promise.resolve = resolve;
47 | promise.reject = reject;
48 |
49 | promiseTable[id] = promise;
50 |
51 | globalThis.__send(op, ...[id, ...args]);
52 |
53 | const res = await promise;
54 | if (res.ok) return res.ok;
55 | else if (res.err) return res.err;
56 | else throw new Error("Unknown error");
57 | }
58 | }
59 |
60 | Object.assign(window, {
61 | __sendAsync,
62 | });
63 | })(globalThis);
64 |
--------------------------------------------------------------------------------
/js/01_namespace.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license.
2 | globalThis.Elsa = {
3 | readFile: (arg) => {
4 | return globalThis.__send(__ops.FSRead, arg);
5 | },
6 | writeFile: (file, content) => {
7 | return globalThis.__send(__ops.FSWrite, file, content);
8 | },
9 | exists: (arg) => {
10 | return globalThis.__send(__ops.FSExists, arg);
11 | },
12 | stat: (arg) => {
13 | return JSON.parse(globalThis.__send(__ops.FSStat, arg));
14 | },
15 | serve: async function (host, cb) {
16 | globalThis.__sendAsync(
17 | __ops.Serve,
18 | function (data) {
19 | return JSON.stringify(cb(JSON.parse(data)));
20 | },
21 | host
22 | );
23 | },
24 | remove: (arg) => {
25 | return globalThis.__send(__ops.FSRemove, arg);
26 | },
27 | cwd: () => {
28 | return globalThis.__send(__ops.FSCwd);
29 | },
30 | runPlugin: (dylib, arg) => {
31 | return globalThis.__send(__ops.Plugin, dylib, arg);
32 | },
33 | mkdir: (arg) => {
34 | return globalThis.__send(__ops.FSMkdir, arg);
35 | },
36 | env: {
37 | get: (arg) => {
38 | return globalThis.__send(__ops.Env, arg);
39 | },
40 | set: (env, val) => {
41 | return globalThis.__send(__ops.Env, env, val);
42 | },
43 | toObject: () => {
44 | return JSON.parse(globalThis.__send(__ops.Env, true));
45 | },
46 | },
47 | *walk(path) {
48 | const files = JSON.parse(globalThis.__send(__ops.Walk, path));
49 |
50 | for (const file of files) {
51 | yield file;
52 | }
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/js/02_console.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license.
2 | // DOM console bindings
3 | globalThis.console = {
4 | trace: (...args) => {
5 | val = args[0];
6 | globalThis.__send(
7 | __ops.Log,
8 | typeof val,
9 | typeof val == "object" ? JSON.stringify(val) : val
10 | );
11 | },
12 | debug: (...args) => {
13 | val = args[0];
14 | globalThis.__send(
15 | __ops.Log,
16 | typeof val,
17 | typeof val == "object" ? JSON.stringify(val) : val
18 | );
19 | },
20 | log: (...args) => {
21 | val = args[0];
22 | globalThis.__send(
23 | __ops.Log,
24 | typeof val,
25 | typeof val == "object" ? JSON.stringify(val) : val
26 | );
27 | },
28 | info: (...args) => {
29 | val = args[0];
30 | globalThis.__send(
31 | __ops.Log,
32 | typeof val,
33 | typeof val == "object" ? JSON.stringify(val) : val
34 | );
35 | },
36 | warn: (...args) => {
37 | val = args[0];
38 | globalThis.__send(
39 | __ops.Log,
40 | typeof val,
41 | typeof val == "object" ? JSON.stringify(val) : val
42 | );
43 | },
44 | error: (...args) => {
45 | val = args[0];
46 | globalThis.__send(
47 | __ops.Log,
48 | typeof val,
49 | typeof val == "object" ? JSON.stringify(val) : val
50 | );
51 | },
52 | };
53 |
--------------------------------------------------------------------------------
/js/03_fetch.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license.
2 | globalThis.fetch = async function (url) {
3 | return globalThis.__sendAsync(__ops.Fetch, false, url);
4 | };
5 |
--------------------------------------------------------------------------------
/js/04_event.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license.
2 |
3 | /**
4 | * Class for managing events.
5 | * Can be extended to provide event functionality in other classes.
6 | *
7 | * @class EventEmitter Manages event registering and emitting.
8 | */
9 | function EventEmitter() {}
10 |
11 | /**
12 | * Finds the index of the listener for the event in its storage array.
13 | *
14 | * @param {Function[]} listeners Array of listeners to search through.
15 | * @param {Function} listener Method to look for.
16 | * @return {Number} Index of the specified listener, -1 if not found
17 | * @api private
18 | */
19 | function indexOfListener(listeners, listener) {
20 | var i = listeners.length;
21 | while (i--) {
22 | if (listeners[i].listener === listener) {
23 | return i;
24 | }
25 | }
26 |
27 | return -1;
28 | }
29 |
30 | /**
31 | * Alias a method while keeping the context correct, to allow for overwriting of target method.
32 | *
33 | * @param {String} name The name of the target method.
34 | * @return {Function} The aliased method
35 | * @api private
36 | */
37 | function alias(name) {
38 | return function aliasClosure() {
39 | return this[name].apply(this, arguments);
40 | };
41 | }
42 |
43 | /**
44 | * Returns the listener array for the specified event.
45 | * Will initialise the event object and listener arrays if required.
46 | * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
47 | * Each property in the object response is an array of listener functions.
48 | *
49 | * @param {String|RegExp} evt Name of the event to return the listeners from.
50 | * @return {Function[]|Object} All listener functions for the event.
51 | */
52 | EventEmitter.prototype.getListeners = function getListeners(evt) {
53 | var events = this._getEvents();
54 | var response;
55 | var key;
56 |
57 | // Return a concatenated array of all matching events if
58 | // the selector is a regular expression.
59 | if (evt instanceof RegExp) {
60 | response = {};
61 | for (key in events) {
62 | if (Object.hasOwnProperty(events, key) && evt.test(key)) {
63 | response[key] = events[key];
64 | }
65 | }
66 | } else {
67 | response = events[evt] || (events[evt] = []);
68 | }
69 |
70 | return response;
71 | };
72 |
73 | /**
74 | * Takes a list of listener objects and flattens it into a list of listener functions.
75 | *
76 | * @param {Object[]} listeners Raw listener objects.
77 | * @return {Function[]} Just the listener functions.
78 | */
79 | EventEmitter.prototype.flattenListeners = function flattenListeners(listeners) {
80 | var flatListeners = [];
81 | var i;
82 |
83 | for (i = 0; i < listeners.length; i += 1) {
84 | flatListeners.push(listeners[i].listener);
85 | }
86 |
87 | return flatListeners;
88 | };
89 |
90 | /**
91 | * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
92 | *
93 | * @param {String|RegExp} evt Name of the event to return the listeners from.
94 | * @return {Object} All listener functions for an event in an object.
95 | */
96 | EventEmitter.prototype.getListenersAsObject = function getListenersAsObject(
97 | evt
98 | ) {
99 | var listeners = this.getListeners(evt);
100 | var response;
101 |
102 | if (listeners instanceof Array) {
103 | response = {};
104 | response[evt] = listeners;
105 | }
106 |
107 | return response || listeners;
108 | };
109 |
110 | function isValidListener(listener) {
111 | if (typeof listener === "function" || listener instanceof RegExp) {
112 | return true;
113 | } else if (listener && typeof listener === "object") {
114 | return isValidListener(listener.listener);
115 | } else {
116 | return false;
117 | }
118 | }
119 |
120 | /**
121 | * Adds a listener function to the specified event.
122 | * The listener will not be added if it is a duplicate.
123 | * If the listener returns true then it will be removed after it is called.
124 | * If you pass a regular expression as the event name then the listener will be added to all events that match it.
125 | *
126 | * @param {String|RegExp} evt Name of the event to attach the listener to.
127 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
128 | * @return {Object} Current instance of EventEmitter for chaining.
129 | */
130 | EventEmitter.prototype.addListener = function addListener(evt, listener) {
131 | if (!isValidListener(listener)) {
132 | throw new TypeError("listener must be a function");
133 | }
134 |
135 | var listeners = this.getListenersAsObject(evt);
136 | var listenerIsWrapped = typeof listener === "object";
137 | var key;
138 |
139 | for (key in listeners) {
140 | if (
141 | Object.hasOwnProperty(listeners, key) &&
142 | indexOfListener(listeners[key], listener) === -1
143 | ) {
144 | listeners[key].push(
145 | listenerIsWrapped
146 | ? listener
147 | : {
148 | listener: listener,
149 | once: false,
150 | }
151 | );
152 | }
153 | }
154 |
155 | return this;
156 | };
157 |
158 | /**
159 | * Alias of addListener
160 | */
161 | EventEmitter.prototype.on = alias("addListener");
162 |
163 | /**
164 | * Semi-alias of addListener. It will add a listener that will be
165 | * automatically removed after its first execution.
166 | *
167 | * @param {String|RegExp} evt Name of the event to attach the listener to.
168 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
169 | * @return {Object} Current instance of EventEmitter for chaining.
170 | */
171 | EventEmitter.prototype.addOnceListener = function addOnceListener(
172 | evt,
173 | listener
174 | ) {
175 | return this.addListener(evt, {
176 | listener: listener,
177 | once: true,
178 | });
179 | };
180 |
181 | /**
182 | * Alias of addOnceListener.
183 | */
184 | EventEmitter.prototype.once = alias("addOnceListener");
185 |
186 | /**
187 | * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
188 | * You need to tell it what event names should be matched by a regex.
189 | *
190 | * @param {String} evt Name of the event to create.
191 | * @return {Object} Current instance of EventEmitter for chaining.
192 | */
193 | EventEmitter.prototype.defineEvent = function defineEvent(evt) {
194 | this.getListeners(evt);
195 | return this;
196 | };
197 |
198 | /**
199 | * Uses defineEvent to define multiple events.
200 | *
201 | * @param {String[]} evts An array of event names to define.
202 | * @return {Object} Current instance of EventEmitter for chaining.
203 | */
204 | EventEmitter.prototype.defineEvents = function defineEvents(evts) {
205 | for (var i = 0; i < evts.length; i += 1) {
206 | this.defineEvent(evts[i]);
207 | }
208 | return this;
209 | };
210 |
211 | /**
212 | * Removes a listener function from the specified event.
213 | * When passed a regular expression as the event name, it will remove the listener from all events that match it.
214 | *
215 | * @param {String|RegExp} evt Name of the event to remove the listener from.
216 | * @param {Function} listener Method to remove from the event.
217 | * @return {Object} Current instance of EventEmitter for chaining.
218 | */
219 | EventEmitter.prototype.removeListener = function removeListener(evt, listener) {
220 | var listeners = this.getListenersAsObject(evt);
221 | var index;
222 | var key;
223 |
224 | for (key in listeners) {
225 | if (Object.hasOwnProperty(listeners, key)) {
226 | index = indexOfListener(listeners[key], listener);
227 |
228 | if (index !== -1) {
229 | listeners[key].splice(index, 1);
230 | }
231 | }
232 | }
233 |
234 | return this;
235 | };
236 |
237 | /**
238 | * Alias of removeListener
239 | */
240 | EventEmitter.prototype.off = alias("removeListener");
241 |
242 | /**
243 | * Adds listeners in bulk using the manipulateListeners method.
244 | * If you pass an object as the first argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
245 | * You can also pass it a regular expression to add the array of listeners to all events that match it.
246 | * Yeah, this function does quite a bit. That's probably a bad thing.
247 | *
248 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
249 | * @param {Function[]} [listeners] An optional array of listener functions to add.
250 | * @return {Object} Current instance of EventEmitter for chaining.
251 | */
252 | EventEmitter.prototype.addListeners = function addListeners(evt, listeners) {
253 | // Pass through to manipulateListeners
254 | return this.manipulateListeners(false, evt, listeners);
255 | };
256 |
257 | /**
258 | * Removes listeners in bulk using the manipulateListeners method.
259 | * If you pass an object as the first argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
260 | * You can also pass it an event name and an array of listeners to be removed.
261 | * You can also pass it a regular expression to remove the listeners from all events that match it.
262 | *
263 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
264 | * @param {Function[]} [listeners] An optional array of listener functions to remove.
265 | * @return {Object} Current instance of EventEmitter for chaining.
266 | */
267 | EventEmitter.prototype.removeListeners = function removeListeners(
268 | evt,
269 | listeners
270 | ) {
271 | // Pass through to manipulateListeners
272 | return this.manipulateListeners(true, evt, listeners);
273 | };
274 |
275 | /**
276 | * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
277 | * The first argument will determine if the listeners are removed (true) or added (false).
278 | * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
279 | * You can also pass it an event name and an array of listeners to be added/removed.
280 | * You can also pass it a regular expression to manipulate the listeners of all events that match it.
281 | *
282 | * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
283 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
284 | * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
285 | * @return {Object} Current instance of EventEmitter for chaining.
286 | */
287 | EventEmitter.prototype.manipulateListeners = function manipulateListeners(
288 | remove,
289 | evt,
290 | listeners
291 | ) {
292 | var i;
293 | var value;
294 | var single = remove ? this.removeListener : this.addListener;
295 | var multiple = remove ? this.removeListeners : this.addListeners;
296 |
297 | // If evt is an object then pass each of its properties to this method
298 | if (typeof evt === "object" && !(evt instanceof RegExp)) {
299 | for (i in evt) {
300 | if (Object.hasOwnProperty(evt, i) && (value = evt[i])) {
301 | // Pass the single listener straight through to the singular method
302 | if (typeof value === "function") {
303 | single.call(this, i, value);
304 | } else {
305 | // Otherwise pass back to the multiple function
306 | multiple.call(this, i, value);
307 | }
308 | }
309 | }
310 | } else {
311 | // So evt must be a string
312 | // And listeners must be an array of listeners
313 | // Loop over it and pass each one to the multiple method
314 | i = listeners.length;
315 | while (i--) {
316 | single.call(this, evt, listeners[i]);
317 | }
318 | }
319 |
320 | return this;
321 | };
322 |
323 | /**
324 | * Removes all listeners from a specified event.
325 | * If you do not specify an event then all listeners will be removed.
326 | * That means every event will be emptied.
327 | * You can also pass a regex to remove all events that match it.
328 | *
329 | * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
330 | * @return {Object} Current instance of EventEmitter for chaining.
331 | */
332 | EventEmitter.prototype.removeEvent = function removeEvent(evt) {
333 | var type = typeof evt;
334 | var events = this._getEvents();
335 | var key;
336 |
337 | // Remove different things depending on the state of evt
338 | if (type === "string") {
339 | // Remove all listeners for the specified event
340 | delete events[evt];
341 | } else if (evt instanceof RegExp) {
342 | // Remove all events matching the regex.
343 | for (key in events) {
344 | if (Object.hasOwnProperty(events, key) && evt.test(key)) {
345 | delete events[key];
346 | }
347 | }
348 | } else {
349 | // Remove all listeners in all events
350 | delete this._events;
351 | }
352 |
353 | return this;
354 | };
355 |
356 | /**
357 | * Alias of removeEvent.
358 | *
359 | * Added to mirror the node API.
360 | */
361 | EventEmitter.prototype.removeAllListeners = alias("removeEvent");
362 |
363 | /**
364 | * Emits an event of your choice.
365 | * When emitted, every listener attached to that event will be executed.
366 | * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
367 | * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
368 | * So they will not arrive within the array on the other side, they will be separate.
369 | * You can also pass a regular expression to emit to all events that match it.
370 | *
371 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
372 | * @param {Array} [args] Optional array of arguments to be passed to each listener.
373 | * @return {Object} Current instance of EventEmitter for chaining.
374 | */
375 | EventEmitter.prototype.emitEvent = function emitEvent(evt, args) {
376 | var listenersMap = this.getListenersAsObject(evt);
377 | var listeners;
378 | var listener;
379 | var i;
380 | var key;
381 | var response;
382 |
383 | for (key in listenersMap) {
384 | if (Object.hasOwnProperty(listenersMap, key)) {
385 | listeners = listenersMap[key].slice(0);
386 |
387 | for (i = 0; i < listeners.length; i++) {
388 | // If the listener returns true then it shall be removed from the event
389 | // The function is executed either with a basic call or an apply if there is an args array
390 | listener = listeners[i];
391 |
392 | if (listener.once === true) {
393 | this.removeListener(evt, listener.listener);
394 | }
395 |
396 | response = listener.listener.apply(this, args || []);
397 |
398 | if (response === this._getOnceReturnValue()) {
399 | this.removeListener(evt, listener.listener);
400 | }
401 | }
402 | }
403 | }
404 |
405 | return this;
406 | };
407 |
408 | /**
409 | * Alias of emitEvent
410 | */
411 | EventEmitter.prototype.trigger = alias("emitEvent");
412 |
413 | /**
414 | * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
415 | * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
416 | *
417 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
418 | * @param {...*} Optional additional arguments to be passed to each listener.
419 | * @return {Object} Current instance of EventEmitter for chaining.
420 | */
421 | EventEmitter.prototype.emit = function emit(evt) {
422 | var args = Array.prototype.slice.call(arguments, 1);
423 | return this.emitEvent(evt, args);
424 | };
425 |
426 | /**
427 | * Sets the current value to check against when executing listeners. If a
428 | * listeners return value matches the one set here then it will be removed
429 | * after execution. This value defaults to true.
430 | *
431 | * @param {*} value The new value to check for when executing listeners.
432 | * @return {Object} Current instance of EventEmitter for chaining.
433 | */
434 | EventEmitter.prototype.setOnceReturnValue = function setOnceReturnValue(value) {
435 | this._onceReturnValue = value;
436 | return this;
437 | };
438 |
439 | /**
440 | * Fetches the current value to check against when executing listeners. If
441 | * the listeners return value matches this one then it should be removed
442 | * automatically. It will return true by default.
443 | *
444 | * @return {*|Boolean} The current value to check for or the default, true.
445 | * @api private
446 | */
447 | EventEmitter.prototype._getOnceReturnValue = function _getOnceReturnValue() {
448 | if (this.hasOwnProperty("_onceReturnValue")) {
449 | return this._onceReturnValue;
450 | } else {
451 | return true;
452 | }
453 | };
454 |
455 | /**
456 | * Fetches the events object and creates one if required.
457 | *
458 | * @return {Object} The events storage object.
459 | * @api private
460 | */
461 | EventEmitter.prototype._getEvents = function _getEvents() {
462 | return this._events || (this._events = {});
463 | };
464 |
465 | globalThis.EventEmitter = EventEmitter;
466 |
--------------------------------------------------------------------------------
/js/05_compiler.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license.
2 | const IGNORED_DIAGNOSTICS = [
3 | // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
4 | // not a module.
5 | 2306,
6 | // TS1375: 'await' expressions are only allowed at the top level of a file
7 | // when that file is a module, but this file has no imports or exports.
8 | // Consider adding an empty 'export {}' to make this file a module.
9 | 1375,
10 | // TS1103: 'for-await-of' statement is only allowed within an async function
11 | // or async generator.
12 | 1103,
13 | // TS2691: An import path cannot end with a '.ts' extension. Consider
14 | // importing 'bad-module' instead.
15 | 2691,
16 | // TS5009: Cannot find the common subdirectory path for the input files.
17 | 5009,
18 | // TS5055: Cannot write file
19 | // 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js'
20 | // because it would overwrite input file.
21 | 5055,
22 | // TypeScript is overly opinionated that only CommonJS modules kinds can
23 | // support JSON imports. Allegedly this was fixed in
24 | // Microsoft/TypeScript#26825 but that doesn't seem to be working here,
25 | // so we will ignore complaints about this compiler setting.
26 | 5070,
27 | // TS7016: Could not find a declaration file for module '...'. '...'
28 | // implicitly has an 'any' type. This is due to `allowJs` being off by
29 | // default but importing of a JavaScript module.
30 | 7016,
31 | ];
32 |
33 | const options = { allowNonTsExtensions: true };
34 |
35 | function typeCheck(file, source) {
36 | const dummyFilePath = file;
37 | const textAst = ts.createSourceFile(
38 | dummyFilePath,
39 | source,
40 | ts.ScriptTarget.ES6
41 | );
42 | const dtsAST = ts.createSourceFile(
43 | "/lib.es6.d.ts",
44 | Asset("typescript/lib.es6.d.ts"),
45 | ts.ScriptTarget.ES6
46 | );
47 |
48 | const files = { [dummyFilePath]: textAst, "/lib.es6.d.ts": dtsAST };
49 | const host = {
50 | fileExists: (filePath) => {
51 | return files[filePath] != null || Elsa.exists(filePath);
52 | },
53 | directoryExists: (dirPath) => dirPath === "/",
54 | getCurrentDirectory: () => Elsa.cwd(),
55 | getDirectories: () => [],
56 | getCanonicalFileName: (fileName) => fileName,
57 | getNewLine: () => "\n",
58 | getDefaultLibFileName: () => "/lib.es6.d.ts",
59 | getSourceFile: (filePath) => {
60 | if (files[filePath] != null) return files[filePath];
61 | else {
62 | return ts.createSourceFile(
63 | filePath,
64 | Elsa.readFile(filePath),
65 | ts.ScriptTarget.ES6
66 | );
67 | }
68 | },
69 | readFile: (filePath) => {
70 | return filePath === dummyFilePath ? text : Elsa.readFile(filePath);
71 | },
72 | useCaseSensitiveFileNames: () => true,
73 | writeFile: () => {},
74 | resolveModuleNames,
75 | };
76 | const program = ts.createProgram({
77 | options,
78 | rootNames: [dummyFilePath],
79 | host,
80 | });
81 |
82 | let diag = ts.getPreEmitDiagnostics(program).filter(function ({ code }) {
83 | return code != 5023 && !IGNORED_DIAGNOSTICS.includes(code);
84 | });
85 | let diags = ts.formatDiagnosticsWithColorAndContext(diag, host);
86 | Report(diags);
87 | }
88 |
89 | function resolveModuleNames(moduleNames, containingFile) {
90 | const resolvedModules = [];
91 | for (const moduleName of moduleNames) {
92 | let fileName = join(containingFile, "..", moduleName);
93 | if (moduleName.startsWith("https://")) {
94 | fileName = moduleName.replace("https://", "/tmp/");
95 | }
96 | resolvedModules.push({ resolvedFileName: fileName });
97 | }
98 | return resolvedModules;
99 | }
100 |
101 | // Joins path segments. Preserves initial "/" and resolves ".." and "."
102 | // Does not support using ".." to go above/outside the root.
103 | // This means that join("foo", "../../bar") will not resolve to "../bar"
104 | function join(/* path segments */) {
105 | // Split the inputs into a list of path commands.
106 | var parts = [];
107 | for (var i = 0, l = arguments.length; i < l; i++) {
108 | parts = parts.concat(arguments[i].split("/"));
109 | }
110 | // Interpret the path commands to get the new resolved path.
111 | var newParts = [];
112 | for (i = 0, l = parts.length; i < l; i++) {
113 | var part = parts[i];
114 | // Remove leading and trailing slashes
115 | // Also remove "." segments
116 | if (!part || part === ".") continue;
117 | // Interpret ".." to pop the last segment
118 | if (part === "..") newParts.pop();
119 | // Push new path segments.
120 | else newParts.push(part);
121 | }
122 | // Preserve the initial slash if there was one.
123 | if (parts[0] === "") newParts.unshift("");
124 | // Turn back into a single string path.
125 | return newParts.join("/") || (newParts.length ? "/" : ".");
126 | }
127 |
--------------------------------------------------------------------------------
/js/06_encoder.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license.
2 | var utf8Encodings = ["utf8", "utf-8", "unicode-1-1-utf-8"];
3 |
4 | function TextEncoder(encoding) {
5 | if (
6 | utf8Encodings.indexOf(encoding) < 0 &&
7 | typeof encoding !== "undefined" &&
8 | encoding != null
9 | ) {
10 | throw new RangeError("Invalid encoding type. Only utf-8 is supported");
11 | } else {
12 | this.encoding = "utf-8";
13 | this.encode = function (str) {
14 | if (typeof str !== "string") {
15 | throw new TypeError("passed argument must be of tye string");
16 | }
17 | var binstr = unescape(encodeURIComponent(str)),
18 | arr = new Uint8Array(binstr.length);
19 | const split = binstr.split("");
20 | for (let i = 0; i < split.length; i++) {
21 | arr[i] = split[i].charCodeAt(0);
22 | }
23 | return arr;
24 | };
25 | }
26 | }
27 |
28 | function TextDecoder(encoding) {
29 | if (
30 | utf8Encodings.indexOf(encoding) < 0 &&
31 | typeof encoding !== "undefined" &&
32 | encoding != null
33 | ) {
34 | throw new RangeError("Invalid encoding type. Only utf-8 is supported");
35 | } else {
36 | this.encoding = "utf-8";
37 | this.decode = function (view, options) {
38 | if (typeof view === "undefined") {
39 | return "";
40 | }
41 |
42 | var stream =
43 | typeof options !== "undefined" && stream in options
44 | ? options.stream
45 | : false;
46 | if (typeof stream !== "boolean") {
47 | throw new TypeError("stream option must be boolean");
48 | }
49 |
50 | if (!ArrayBuffer.isView(view)) {
51 | throw new TypeError("passed argument must be an array buffer view");
52 | } else {
53 | var arr = new Uint8Array(view.buffer, view.byteOffset, view.byteLength),
54 | charArr = new Array(arr.length);
55 | for (let i = 0; i < arr.length; i++) {
56 | charArr[i] = String.fromCharCode(arr[i]);
57 | }
58 | return decodeURIComponent(escape(charArr.join("")));
59 | }
60 | };
61 | }
62 | }
63 |
64 | /**
65 | * Implementation of atob() according to the HTML and Infra specs, except that
66 | * instead of throwing INVALID_CHARACTER_ERR we return null.
67 | */
68 | function atob(data) {
69 | // Web IDL requires DOMStrings to just be converted using ECMAScript
70 | // ToString, which in our case amounts to using a template literal.
71 | data = `${data}`;
72 | // "Remove all ASCII whitespace from data."
73 | data = data.replace(/[ \t\n\f\r]/g, "");
74 | // "If data's length divides by 4 leaving no remainder, then: if data ends
75 | // with one or two U+003D (=) code points, then remove them from data."
76 | if (data.length % 4 === 0) {
77 | data = data.replace(/==?$/, "");
78 | }
79 | // "If data's length divides by 4 leaving a remainder of 1, then return
80 | // failure."
81 | //
82 | // "If data contains a code point that is not one of
83 | //
84 | // U+002B (+)
85 | // U+002F (/)
86 | // ASCII alphanumeric
87 | //
88 | // then return failure."
89 | if (data.length % 4 === 1 || /[^+/0-9A-Za-z]/.test(data)) {
90 | return null;
91 | }
92 | // "Let output be an empty byte sequence."
93 | let output = "";
94 | // "Let buffer be an empty buffer that can have bits appended to it."
95 | //
96 | // We append bits via left-shift and or. accumulatedBits is used to track
97 | // when we've gotten to 24 bits.
98 | let buffer = 0;
99 | let accumulatedBits = 0;
100 | // "Let position be a position variable for data, initially pointing at the
101 | // start of data."
102 | //
103 | // "While position does not point past the end of data:"
104 | for (let i = 0; i < data.length; i++) {
105 | // "Find the code point pointed to by position in the second column of
106 | // Table 1: The Base 64 Alphabet of RFC 4648. Let n be the number given in
107 | // the first cell of the same row.
108 | //
109 | // "Append to buffer the six bits corresponding to n, most significant bit
110 | // first."
111 | //
112 | // atobLookup() implements the table from RFC 4648.
113 | buffer <<= 6;
114 | buffer |= atobLookup(data[i]);
115 | accumulatedBits += 6;
116 | // "If buffer has accumulated 24 bits, interpret them as three 8-bit
117 | // big-endian numbers. Append three bytes with values equal to those
118 | // numbers to output, in the same order, and then empty buffer."
119 | if (accumulatedBits === 24) {
120 | output += String.fromCharCode((buffer & 0xff0000) >> 16);
121 | output += String.fromCharCode((buffer & 0xff00) >> 8);
122 | output += String.fromCharCode(buffer & 0xff);
123 | buffer = accumulatedBits = 0;
124 | }
125 | // "Advance position by 1."
126 | }
127 | // "If buffer is not empty, it contains either 12 or 18 bits. If it contains
128 | // 12 bits, then discard the last four and interpret the remaining eight as
129 | // an 8-bit big-endian number. If it contains 18 bits, then discard the last
130 | // two and interpret the remaining 16 as two 8-bit big-endian numbers. Append
131 | // the one or two bytes with values equal to those one or two numbers to
132 | // output, in the same order."
133 | if (accumulatedBits === 12) {
134 | buffer >>= 4;
135 | output += String.fromCharCode(buffer);
136 | } else if (accumulatedBits === 18) {
137 | buffer >>= 2;
138 | output += String.fromCharCode((buffer & 0xff00) >> 8);
139 | output += String.fromCharCode(buffer & 0xff);
140 | }
141 | // "Return output."
142 | return output;
143 | }
144 | /**
145 | * A lookup table for atob(), which converts an ASCII character to the
146 | * corresponding six-bit number.
147 | */
148 |
149 | const keystr =
150 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
151 |
152 | function atobLookup(chr) {
153 | const index = keystr.indexOf(chr);
154 | // Throw exception if character is not in the lookup string; should not be hit in tests
155 | return index < 0 ? undefined : index;
156 | }
157 |
158 | /**
159 | * btoa() as defined by the HTML and Infra specs, which mostly just references
160 | * RFC 4648.
161 | */
162 | function btoa(s) {
163 | let i;
164 | // String conversion as required by Web IDL.
165 | s = `${s}`;
166 | // "The btoa() method must throw an "InvalidCharacterError" DOMException if
167 | // data contains any character whose code point is greater than U+00FF."
168 | for (i = 0; i < s.length; i++) {
169 | if (s.charCodeAt(i) > 255) {
170 | return null;
171 | }
172 | }
173 | let out = "";
174 | for (i = 0; i < s.length; i += 3) {
175 | const groupsOfSix = [undefined, undefined, undefined, undefined];
176 | groupsOfSix[0] = s.charCodeAt(i) >> 2;
177 | groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;
178 | if (s.length > i + 1) {
179 | groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;
180 | groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;
181 | }
182 | if (s.length > i + 2) {
183 | groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;
184 | groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;
185 | }
186 | for (let j = 0; j < groupsOfSix.length; j++) {
187 | if (typeof groupsOfSix[j] === "undefined") {
188 | out += "=";
189 | } else {
190 | out += btoaLookup(groupsOfSix[j]);
191 | }
192 | }
193 | }
194 | return out;
195 | }
196 |
197 | function btoaLookup(index) {
198 | if (index >= 0 && index < 64) {
199 | return keystr[index];
200 | }
201 |
202 | // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
203 | return undefined;
204 | }
205 |
206 | globalThis.TextEncoder = TextEncoder;
207 | globalThis.TextDecoder = TextDecoder;
208 | globalThis.atob = atob;
209 | globalThis.btoa = btoa;
210 |
--------------------------------------------------------------------------------
/js/07_testing.js:
--------------------------------------------------------------------------------
1 | Elsa.tests = function (tests) {
2 | // Run tests only when mode is `test`
3 | // Will not run when running a script
4 | if (Elsa.mode !== "test") return;
5 |
6 | // Keep track for failures
7 | let failures = 0;
8 | // Loop through tests and run the function passed.
9 | // It basically `try...catch`'s the run and determines its failure.
10 | // No additional helper utilities are provided.
11 | let passed = 0;
12 | const startTestTime = Date.now();
13 |
14 | const red = "\u001b[31m";
15 | const green = "\u001b[32m";
16 | const reset = "\u001b[0m";
17 | const gray = "\u001b[38;5;8m";
18 | const greenBG = "\u001b[42m";
19 | const redBG = "\u001b[41m";
20 |
21 | for (let testName in tests) {
22 | let testAction = tests[testName];
23 | const timeBeforeStart = Date.now();
24 | try {
25 | testAction();
26 |
27 | console.log(
28 | `TEST ${testName} ... ${greenBG} OK ${reset} ${gray}(${
29 | Date.now() - timeBeforeStart
30 | }ms)${reset}\n`
31 | );
32 |
33 | passed++;
34 | } catch (e) {
35 | failures++;
36 | console.error(
37 | `TEST ${testName} ... ${redBG} FAILED ${reset} ${gray}(${
38 | Date.now() - timeBeforeStart
39 | }ms)${reset} \n ${e}`
40 | );
41 | console.error(e.stack);
42 | }
43 | }
44 | const endTestTime = Date.now() - startTestTime;
45 |
46 | console.log(
47 | `TEST results: ${passed} passed, ${failures} failed, ${
48 | passed + failures
49 | } total, ${gray}(${endTestTime}ms)${reset}`
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/js/10_window.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 elsa.land authors. All rights reserved. MIT license.
2 | // window.Elsa = globalThis.Elsa = Elsa
3 | globalThis.window = globalThis;
4 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/elsaland/elsa/dev"
5 | "runtime"
6 |
7 | "github.com/elsaland/elsa/bundler"
8 | "github.com/elsaland/elsa/cmd"
9 | "github.com/elsaland/elsa/core"
10 | )
11 |
12 | func main() {
13 | runtime.LockOSThread()
14 | cmd.Execute(cmd.Elsa{
15 | Run: core.Run,
16 | Bundle: bundler.BundleModule,
17 | Dev: dev.RunDev,
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/mod.toml:
--------------------------------------------------------------------------------
1 | [module]
2 | name = "elsa"
3 | version = "0.0.0"
4 | authors = ["the elsaland team"]
5 | license = "MIT"
6 |
7 | [options]
8 | no_color = false
9 |
--------------------------------------------------------------------------------
/module/config.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "github.com/Netflix/go-env"
5 | "github.com/pelletier/go-toml"
6 | "io/ioutil"
7 | "os"
8 | )
9 |
10 | type module struct {
11 | Name string `toml:"name"`
12 | Desc string `toml:"description"`
13 | Version string `toml:"version"`
14 | License string `toml:"license"`
15 | Authors []string `toml:"authors"`
16 | }
17 |
18 | type options struct {
19 | NoColor bool `toml:"no_color" env:"NO_COLOR"`
20 | }
21 |
22 | type Config struct {
23 | Module module `toml:"module"`
24 | Options options `toml:"options"`
25 | }
26 |
27 | var DefaultConfigPath = "mod.toml"
28 |
29 | func ConfigLoad() (*Config, error) {
30 | cfg := Config{}
31 |
32 | if _, err := os.Stat(DefaultConfigPath); err == nil || os.IsExist(err) {
33 | buf, err := ioutil.ReadFile(DefaultConfigPath)
34 | if err != nil {
35 | return nil, err
36 | }
37 | err = toml.Unmarshal(buf, &cfg)
38 | if err != nil {
39 | return nil, err
40 | }
41 | }
42 |
43 | _, err := env.UnmarshalFromEnviron(&cfg)
44 | if err != nil {
45 | return nil, err
46 | }
47 |
48 | return &cfg, nil
49 | }
50 |
51 | func ConfigParse(source []byte) (*Config, error) {
52 | cfg := Config{}
53 |
54 | if source != nil {
55 | err := toml.Unmarshal(source, &cfg)
56 | if err != nil {
57 | return nil, err
58 | }
59 | }
60 |
61 | _, err := env.UnmarshalFromEnviron(&cfg)
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | return &cfg, nil
67 | }
68 |
69 | func ConfigExists() bool {
70 | if _, err := os.Stat(DefaultConfigPath); err == nil || os.IsExist(err) {
71 | return true
72 | }
73 | return false
74 | }
75 |
--------------------------------------------------------------------------------
/packager/exec.go:
--------------------------------------------------------------------------------
1 | package packager
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "os"
7 | "os/exec"
8 | )
9 |
10 | func ExecBuild(path string) {
11 | cmd := exec.Command("go", "build", ".")
12 | cmd.Dir = path
13 | var out bytes.Buffer
14 | var stderr bytes.Buffer
15 | cmd.Stdout = &out
16 | cmd.Stderr = &stderr
17 | err := cmd.Run()
18 | if err != nil {
19 | fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
20 | os.Exit(1)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packager/packager.go:
--------------------------------------------------------------------------------
1 | package packager
2 |
3 | import (
4 | "github.com/elsaland/elsa/module"
5 | "github.com/elsaland/elsa/util"
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/go-bindata/go-bindata"
10 | )
11 |
12 | // PkgSource pack bundled js source into an executable
13 | func PkgSource(source string) {
14 | c := bindata.NewConfig()
15 |
16 | input := parseInput(source)
17 | if module.ConfigExists() {
18 | config := parseInput(module.DefaultConfigPath)
19 | c.Input = []bindata.InputConfig{input, config}
20 | } else {
21 | c.Input = []bindata.InputConfig{input}
22 | }
23 | c.Output = "target/elsa-package/asset.go"
24 |
25 | err := bindata.Translate(c)
26 | util.Check(err)
27 |
28 | entry := GeneratePkgSource(source)
29 | f, _ := os.Create("target/elsa-package/main.go")
30 |
31 | defer f.Close()
32 | _, err = f.WriteString(entry)
33 | util.Check(err)
34 |
35 | ExecBuild("target/elsa-package")
36 | }
37 |
38 | func parseInput(path string) bindata.InputConfig {
39 | return bindata.InputConfig{
40 | Path: filepath.Clean(path),
41 | Recursive: false,
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packager/template.go:
--------------------------------------------------------------------------------
1 | package packager
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/elsaland/elsa/module"
7 | )
8 |
9 | const source string = `
10 | package main
11 |
12 | import (
13 | "os"
14 | "github.com/elsaland/elsa/core"
15 | "github.com/elsaland/elsa/module"
16 | "github.com/elsaland/elsa/core/options"
17 | )
18 |
19 | func main() {
20 | snap, _ := Asset("%s")
21 | toml, _ := Asset("%s")
22 | config, _ := module.ConfigParse(toml)
23 | env := options.Environment{
24 | NoColor: config.Options.NoColor,
25 | Args: os.Args[1:],
26 | }
27 | opt := options.Options{
28 | SourceFile: "elsa.js",
29 | Source: string(snap),
30 | Perms: &options.Perms{Fs: true},
31 | Env: env,
32 | }
33 | core.Run(opt)
34 | }
35 | `
36 |
37 | func GeneratePkgSource(path string) string {
38 | return fmt.Sprintf(source, path, module.DefaultConfigPath)
39 | }
40 |
--------------------------------------------------------------------------------
/std/README.md:
--------------------------------------------------------------------------------
1 | ## Standard modules
2 |
3 | Elsa's standard modules are largely inspired and ported from [Deno](https://deno.land/std) and Go's std
4 |
5 |
6 |
--------------------------------------------------------------------------------
/std/async/README.md:
--------------------------------------------------------------------------------
1 | # `async`
2 |
3 | `async` is a module to provide help with aysncronous tasks.
4 |
5 | The async module has been _completely_ ported from Deno's standard modules.
6 |
7 | - Deferred - Creates a Promise with the `reject` and `resolve` functions.
8 | - Delay - Resolve a Promise after a given amount of milliseconds
9 | - MuxAsyncIterator - The MuxAsyncIterator class multiplexes multiple async iterators into a single
10 | stream. The class makes an assumption that the final result (the value returned and not
11 | yielded from the iterator) does not matter. If there is any result, it is
12 | discarded.
13 | - pooledMap - Transform values from an (async) iterable into another async iterable. The
14 | transforms are done concurrently, with a max concurrency defined by the
15 | poolLimit.
16 |
--------------------------------------------------------------------------------
/std/async/deferred.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | // TODO(ry) It'd be better to make Deferred a class that inherits from
3 | // Promise, rather than an interface. This is possible in ES2016, however
4 | // TypeScript produces broken code when targeting ES5 code.
5 | // See https://github.com/Microsoft/TypeScript/issues/15202
6 | // At the time of writing, the github issue is closed but the problem remains.
7 | export interface Deferred extends Promise {
8 | resolve: (value?: T | PromiseLike) => void;
9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
10 | reject: (reason?: any) => void;
11 | }
12 | /** Creates a Promise with the `reject` and `resolve` functions
13 | * placed as methods on the promise object itself. It allows you to do:
14 | *
15 | * const p = deferred();
16 | * // ...
17 | * p.resolve(42);
18 | */
19 | export function deferred(): Deferred {
20 | let methods;
21 | const promise = new Promise((resolve, reject): void => {
22 | methods = { resolve, reject };
23 | });
24 | return Object.assign(promise, methods) as Deferred;
25 | }
26 |
--------------------------------------------------------------------------------
/std/async/deferred_test.ts:
--------------------------------------------------------------------------------
1 | import { deferred } from "./deferred.ts";
2 |
3 | Elsa.tests({
4 | "[async] deferred: resolve": async function () {
5 | const d = deferred();
6 | d.resolve("❄");
7 | if ((await d) !== "❄") throw new Error("Assertion failed");
8 | },
9 | "[async] deferred: reject": async function (): Promise {
10 | const d = deferred();
11 | d.reject(new Error("An elsa error ❄"));
12 | d.then(() => {
13 | throw new Error("should fail");
14 | });
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/std/async/delay.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | /* Resolves after the given number of milliseconds. */
3 | export function delay(ms: number): Promise {
4 | return new Promise((res): number =>
5 | setTimeout((): void => {
6 | res();
7 | }, ms)
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/std/async/mux_async_iterator.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | import { Deferred, deferred } from "./deferred.ts";
3 |
4 | interface TaggedYieldedValue {
5 | iterator: AsyncIterableIterator;
6 | value: T;
7 | }
8 | /** The MuxAsyncIterator class multiplexes multiple async iterators into a
9 | * single stream. It currently makes an assumption:
10 | * - The final result (the value returned and not yielded from the iterator)
11 | * does not matter; if there is any, it is discarded.
12 | */
13 | export class MuxAsyncIterator implements AsyncIterable {
14 | private iteratorCount = 0;
15 | private yields: Array> = [];
16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
17 | private throws: any[] = [];
18 | private signal: Deferred = deferred();
19 |
20 | add(iterator: AsyncIterableIterator): void {
21 | ++this.iteratorCount;
22 | this.callIteratorNext(iterator);
23 | }
24 |
25 | private async callIteratorNext(
26 | iterator: AsyncIterableIterator
27 | ): Promise {
28 | try {
29 | const { value, done } = await iterator.next();
30 | if (done) {
31 | --this.iteratorCount;
32 | } else {
33 | this.yields.push({ iterator, value });
34 | }
35 | } catch (e) {
36 | this.throws.push(e);
37 | }
38 | this.signal.resolve();
39 | }
40 |
41 | async *iterate(): AsyncIterableIterator {
42 | while (this.iteratorCount > 0) {
43 | // Sleep until any of the wrapped iterators yields.
44 | await this.signal;
45 |
46 | // Note that while we're looping over `yields`, new items may be added.
47 | for (let i = 0; i < this.yields.length; i++) {
48 | const { iterator, value } = this.yields[i];
49 | yield value;
50 | this.callIteratorNext(iterator);
51 | }
52 |
53 | if (this.throws.length) {
54 | for (const e of this.throws) {
55 | throw e;
56 | }
57 | this.throws.length = 0;
58 | }
59 | // Clear the `yields` list and reset the `signal` promise.
60 | this.yields.length = 0;
61 | this.signal = deferred();
62 | }
63 | }
64 |
65 | [Symbol.asyncIterator](): AsyncIterableIterator {
66 | return this.iterate();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/std/async/pool.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | /**
3 | * pooledMap transforms values from an (async) iterable into another async
4 | * iterable. The transforms are done concurrently, with a max concurrency
5 | * defined by the poolLimit.
6 | *
7 | * @param poolLimit The maximum count of items being processed concurrently.
8 | * @param array The input array for mapping.
9 | * @param iteratorFn The function to call for every item of the array.
10 | */
11 | export function pooledMap(
12 | poolLimit: number,
13 | array: Iterable | AsyncIterable,
14 | iteratorFn: (data: T) => Promise
15 | ): AsyncIterableIterator {
16 | // Create the async iterable that is returned from this function.
17 | const res = new TransformStream, R>({
18 | async transform(
19 | p: Promise,
20 | controller: TransformStreamDefaultController
21 | ): Promise {
22 | controller.enqueue(await p);
23 | },
24 | });
25 | // Start processing items from the iterator
26 | (async (): Promise => {
27 | const writer = res.writable.getWriter();
28 | const executing: Array> = [];
29 | for await (const item of array) {
30 | const p = Promise.resolve().then(() => iteratorFn(item));
31 | writer.write(p);
32 | const e: Promise = p.then(() =>
33 | executing.splice(executing.indexOf(e), 1)
34 | );
35 | executing.push(e);
36 | if (executing.length >= poolLimit) {
37 | await Promise.race(executing);
38 | }
39 | }
40 | // Wait until all ongoing events have processed, then close the writer.
41 | await Promise.all(executing);
42 | writer.close();
43 | })();
44 | return res.readable.getIterator();
45 | }
46 |
--------------------------------------------------------------------------------
/std/bytes/README.md:
--------------------------------------------------------------------------------
1 | # `bytes`
2 |
3 | The `bytes` module is made to provide helpers to manipulation of bytes slice. Ported from Deno's std.
4 |
--------------------------------------------------------------------------------
/std/bytes/bytes.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | /** Find first index of binary pattern from a. If not found, then return -1
3 | * @param source source array
4 | * @param pat pattern to find in source array
5 | */
6 | export function findIndex(source: Uint8Array, pat: Uint8Array): number {
7 | const s = pat[0];
8 | for (let i = 0; i < source.length; i++) {
9 | if (source[i] !== s) continue;
10 | const pin = i;
11 | let matched = 1;
12 | let j = i;
13 | while (matched < pat.length) {
14 | j++;
15 | if (source[j] !== pat[j - pin]) {
16 | break;
17 | }
18 | matched++;
19 | }
20 | if (matched === pat.length) {
21 | return pin;
22 | }
23 | }
24 | return -1;
25 | }
26 | /** Find last index of binary pattern from a. If not found, then return -1.
27 | * @param source source array
28 | * @param pat pattern to find in source array
29 | */
30 | export function findLastIndex(source: Uint8Array, pat: Uint8Array): number {
31 | const e = pat[pat.length - 1];
32 | for (let i = source.length - 1; i >= 0; i--) {
33 | if (source[i] !== e) continue;
34 | const pin = i;
35 | let matched = 1;
36 | let j = i;
37 | while (matched < pat.length) {
38 | j--;
39 | if (source[j] !== pat[pat.length - 1 - (pin - j)]) {
40 | break;
41 | }
42 | matched++;
43 | }
44 | if (matched === pat.length) {
45 | return pin - pat.length + 1;
46 | }
47 | }
48 | return -1;
49 | }
50 | /** Check whether binary arrays are equal to each other.
51 | * @param source first array to check equality
52 | * @param match second array to check equality
53 | */
54 | export function equal(source: Uint8Array, match: Uint8Array): boolean {
55 | if (source.length !== match.length) return false;
56 | for (let i = 0; i < match.length; i++) {
57 | if (source[i] !== match[i]) return false;
58 | }
59 | return true;
60 | }
61 | /** Check whether binary array starts with prefix.
62 | * @param source srouce array
63 | * @param prefix prefix array to check in source
64 | */
65 | export function hasPrefix(source: Uint8Array, prefix: Uint8Array): boolean {
66 | for (let i = 0, max = prefix.length; i < max; i++) {
67 | if (source[i] !== prefix[i]) return false;
68 | }
69 | return true;
70 | }
71 | /** Check whether binary array ends with suffix.
72 | * @param source source array
73 | * @param suffix suffix array to check in source
74 | */
75 | export function hasSuffix(source: Uint8Array, suffix: Uint8Array): boolean {
76 | for (
77 | let srci = source.length - 1, sfxi = suffix.length - 1;
78 | sfxi >= 0;
79 | srci--, sfxi--
80 | ) {
81 | if (source[srci] !== suffix[sfxi]) return false;
82 | }
83 | return true;
84 | }
85 | /** Repeat bytes. returns a new byte slice consisting of `count` copies of `b`.
86 | * @param origin The origin bytes
87 | * @param count The count you want to repeat.
88 | */
89 | export function repeat(origin: Uint8Array, count: number): Uint8Array {
90 | if (count === 0) {
91 | return new Uint8Array();
92 | }
93 |
94 | if (count < 0) {
95 | throw new Error("bytes: negative repeat count");
96 | } else if ((origin.length * count) / count !== origin.length) {
97 | throw new Error("bytes: repeat count causes overflow");
98 | }
99 |
100 | const int = Math.floor(count);
101 |
102 | if (int !== count) {
103 | throw new Error("bytes: repeat count must be an integer");
104 | }
105 |
106 | const nb = new Uint8Array(origin.length * count);
107 |
108 | let bp = copyBytes(origin, nb);
109 |
110 | for (; bp < nb.length; bp *= 2) {
111 | copyBytes(nb.slice(0, bp), nb, bp);
112 | }
113 |
114 | return nb;
115 | }
116 | /** Concatenate two binary arrays and return new one.
117 | * @param origin origin array to concatenate
118 | * @param b array to concatenate with origin
119 | */
120 | export function concat(origin: Uint8Array, b: Uint8Array): Uint8Array {
121 | const output = new Uint8Array(origin.length + b.length);
122 | output.set(origin, 0);
123 | output.set(b, origin.length);
124 | return output;
125 | }
126 | /** Check source array contains pattern array.
127 | * @param source source array
128 | * @param pat patter array
129 | */
130 | export function contains(source: Uint8Array, pat: Uint8Array): boolean {
131 | return findIndex(source, pat) != -1;
132 | }
133 | /**
134 | * Copy bytes from one Uint8Array to another. Bytes from `src` which don't fit
135 | * into `dst` will not be copied.
136 | *
137 | * @param src Source byte array
138 | * @param dst Destination byte array
139 | * @param off Offset into `dst` at which to begin writing values from `src`.
140 | * @return number of bytes copied
141 | */
142 | export function copyBytes(src: Uint8Array, dst: Uint8Array, off = 0): number {
143 | off = Math.max(0, Math.min(off, dst.byteLength));
144 | const dstBytesAvailable = dst.byteLength - off;
145 | if (src.byteLength > dstBytesAvailable) {
146 | src = src.subarray(0, dstBytesAvailable);
147 | }
148 | dst.set(src, off);
149 | return src.byteLength;
150 | }
151 |
--------------------------------------------------------------------------------
/std/encoding/hex.ts:
--------------------------------------------------------------------------------
1 | // Ported from Go
2 | // https://github.com/golang/go/blob/go1.12.5/src/encoding/hex/hex.go
3 | // Copyright 2009 The Go Authors. All rights reserved.
4 | // Use of this source code is governed by a BSD-style
5 | // license that can be found in the LICENSE file.
6 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
7 |
8 | const hextable = new TextEncoder().encode("0123456789abcdef");
9 |
10 | export function errInvalidByte(byte: number): Error {
11 | return new Error(
12 | "encoding/hex: invalid byte: " +
13 | new TextDecoder().decode(new Uint8Array([byte]))
14 | );
15 | }
16 |
17 | export function errLength(): Error {
18 | return new Error("encoding/hex: odd length hex string");
19 | }
20 |
21 | // fromHexChar converts a hex character into its value.
22 | function fromHexChar(byte: number): number {
23 | // '0' <= byte && byte <= '9'
24 | if (48 <= byte && byte <= 57) return byte - 48;
25 | // 'a' <= byte && byte <= 'f'
26 | if (97 <= byte && byte <= 102) return byte - 97 + 10;
27 | // 'A' <= byte && byte <= 'F'
28 | if (65 <= byte && byte <= 70) return byte - 65 + 10;
29 |
30 | throw errInvalidByte(byte);
31 | }
32 | /**
33 | * EncodedLen returns the length of an encoding of n source bytes. Specifically,
34 | * it returns n * 2.
35 | * @param n
36 | */
37 | export function encodedLen(n: number): number {
38 | return n * 2;
39 | }
40 | /**
41 | * Encode encodes `src` into `encodedLen(src.length)` bytes.
42 | * @param src
43 | */
44 | export function encode(src: Uint8Array): Uint8Array {
45 | const dst = new Uint8Array(encodedLen(src.length));
46 | for (let i = 0; i < dst.length; i++) {
47 | const v = src[i];
48 | dst[i * 2] = hextable[v >> 4];
49 | dst[i * 2 + 1] = hextable[v & 0x0f];
50 | }
51 | return dst;
52 | }
53 | /**
54 | * EncodeToString returns the hexadecimal encoding of `src`.
55 | * @param src
56 | */
57 | export function encodeToString(src: Uint8Array): string {
58 | return new TextDecoder().decode(encode(src));
59 | }
60 | /**
61 | * Decode decodes `src` into `decodedLen(src.length)` bytes
62 | * If the input is malformed an error will be thrown
63 | * the error.
64 | * @param src
65 | */
66 | export function decode(src: Uint8Array): Uint8Array {
67 | const dst = new Uint8Array(decodedLen(src.length));
68 | for (let i = 0; i < dst.length; i++) {
69 | const a = fromHexChar(src[i * 2]);
70 | const b = fromHexChar(src[i * 2 + 1]);
71 | dst[i] = (a << 4) | b;
72 | }
73 |
74 | if (src.length % 2 == 1) {
75 | // Check for invalid char before reporting bad length,
76 | // since the invalid char (if present) is an earlier problem.
77 | fromHexChar(src[dst.length * 2]);
78 | throw errLength();
79 | }
80 |
81 | return dst;
82 | }
83 | /**
84 | * DecodedLen returns the length of decoding `x` source bytes.
85 | * Specifically, it returns `x / 2`.
86 | * @param x
87 | */
88 | export function decodedLen(x: number): number {
89 | return x >>> 1;
90 | }
91 | /**
92 | * DecodeString returns the bytes represented by the hexadecimal string `s`.
93 | * DecodeString expects that src contains only hexadecimal characters and that
94 | * src has even length.
95 | * If the input is malformed, DecodeString will throw an error.
96 | * @param s the `string` to decode to `Uint8Array`
97 | */
98 | export function decodeString(s: string): Uint8Array {
99 | return decode(new TextEncoder().encode(s));
100 | }
101 |
--------------------------------------------------------------------------------
/std/fmt/colors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) elsa land 2020.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | */
8 |
9 | const _red = "\u001b[31m";
10 | const _black = "\u001b[30m";
11 | const _green = "\u001b[32m";
12 | const _yellow = "\u001b[33m";
13 | const _blue = "\u001b[34m";
14 | const _magenta = "\u001b[35m";
15 | const _cyan = "\u001b[36m";
16 | const _white = "\u001b[37m";
17 | const _reset = "\u001b[0m";
18 |
19 | // * 16bits colors
20 | const _brightBlack = "\u001b[30;1m";
21 | const _brightRed = "\u001b[31;1m";
22 | const _brightGreen = "\u001b[32;1m";
23 | const _brightYellow = "\u001b[33;1m";
24 | const _brightBlue = "\u001b[34;1m";
25 | const _brightMagenta = "\u001b[35;1m";
26 | const _brightCyan = "\u001b[36;1m";
27 | const _brightWhite = "\u001b[37;1m";
28 |
29 | // * backgroud color
30 | const _backgroundBlack = "\u001b[40m";
31 | const _backgroundRed = "\u001b[41m";
32 | const _backgroundGreen = "\u001b[42m";
33 | const _backgroundYellow = "\u001b[43m";
34 | const _backgroundBlue = "\u001b[44m";
35 | const _backgroundMagenta = "\u001b[45m";
36 | const _backgroundCyan = "\u001b[46m";
37 | const _backgroundWhite = "\u001b[47m";
38 |
39 | // * backgroud Bright color
40 | const _backgroundBrightBlack = "\u001b[40;1m";
41 | const _backgroundBrightRed = "\u001b[41;1m";
42 | const _backgroundBrightGreen = "\u001b[42;1m";
43 | const _backgroundBrightYellow = "\u001b[43;1m";
44 | const _backgroundBrightBlue = "\u001b[44;1m";
45 | const _backgroundBrightMagenta = "\u001b[45;1m";
46 | const _backgroundBrightCyan = "\u001b[46;1m";
47 | const _backgroundBrightWhite = "\u001b[47;1m";
48 |
49 | // * decorations
50 | const _bold = "\u001b[1m";
51 | const _underline = "\u001b[4m";
52 | const _reversed = "\u001b[7m";
53 |
54 | /**
55 | * 8bits red color
56 | * @param text
57 | */
58 | export function red(text: string) {
59 | return `${_red}${text}${_reset}`;
60 | }
61 |
62 | /**
63 | * 8bits black color
64 | * @param text
65 | */
66 | export function black(text: string) {
67 | return `${_black}${text}${_reset}`;
68 | }
69 |
70 | /**
71 | * 8bits green color
72 | * @param text
73 | */
74 | export function green(text: string) {
75 | return `${_green}${text}${_reset}`;
76 | }
77 |
78 | /**
79 | * 8bits yellow color
80 | * @param text
81 | */
82 | export function yellow(text: string) {
83 | return `${_yellow}${text}${_reset}`;
84 | }
85 |
86 | /**
87 | * 8bits blue color
88 | * @param text
89 | */
90 | export function blue(text: string) {
91 | return `${_blue}${text}${_reset}`;
92 | }
93 |
94 | /**
95 | * 8bits magenta color
96 | * @param text
97 | */
98 | export function magenta(text: string) {
99 | return `${_magenta}${text}${_reset}`;
100 | }
101 |
102 | /**
103 | * 8bits cyan color
104 | * @param text
105 | */
106 | export function cyan(text: string) {
107 | return `${_cyan}${text}${_reset}`;
108 | }
109 |
110 | /**
111 | * 8bits white color
112 | * @param text
113 | */
114 | export function white(text: string) {
115 | return `${_white}${text}${_reset}`;
116 | }
117 |
118 | /**
119 | * brightBlack color
120 | * @param text
121 | */
122 | export function brightBlack(text: string) {
123 | return `${_brightBlack}${text}${_reset}`;
124 | }
125 |
126 | /**
127 | * brightRed color
128 | * @param text
129 | */
130 | export function brightRed(text: string) {
131 | return `${_brightRed}${text}${_reset}`;
132 | }
133 |
134 | /**
135 | * brightGreen color
136 | * @param text
137 | */
138 | export function brightGreen(text: string) {
139 | return `${_brightGreen}${text}${_reset}`;
140 | }
141 |
142 | /**
143 | * brightYellow color
144 | * @param text
145 | */
146 | export function brightYellow(text: string) {
147 | return `${_brightYellow}${text}${_reset}`;
148 | }
149 |
150 | /**
151 | * brightBlue color
152 | * @param text
153 | */
154 | export function brightBlue(text: string) {
155 | return `${_brightBlue}${text}${_reset}`;
156 | }
157 |
158 | /**
159 | * brightMagenta color
160 | * @param text
161 | */
162 | export function brightMagenta(text: string) {
163 | return `${_brightMagenta}${text}${_reset}`;
164 | }
165 |
166 | /**
167 | * brightCyan color
168 | * @param text
169 | */
170 | export function brightCyan(text: string) {
171 | return `${_brightCyan}${text}${_reset}`;
172 | }
173 |
174 | /**
175 | * brightWhite color
176 | * @param text
177 | */
178 | export function brightWhite(text: string) {
179 | return `${_brightWhite}${text}${_reset}`;
180 | }
181 |
182 | /**
183 | * backgroundBlack color
184 | * @param text
185 | */
186 | export function backgroundBlack(text: string) {
187 | return `${_backgroundBlack}${text}${_reset}`;
188 | }
189 |
190 | /**
191 | * backgroundRed color
192 | * @param text
193 | */
194 | export function backgroundRed(text: string) {
195 | return `${_backgroundRed}${text}${_reset}`;
196 | }
197 |
198 | /**
199 | * backgroundGreen color
200 | * @param text
201 | */
202 | export function backgroundGreen(text: string) {
203 | return `${_backgroundGreen}${text}${_reset}`;
204 | }
205 |
206 | /**
207 | * backgroundYellow color
208 | * @param text
209 | */
210 | export function backgroundYellow(text: string) {
211 | return `${_backgroundYellow}${text}${_reset}`;
212 | }
213 |
214 | /**
215 | * backgroundBlue color
216 | * @param text
217 | */
218 | export function backgroundBlue(text: string) {
219 | return `${_backgroundBlue}${text}${_reset}`;
220 | }
221 |
222 | /**
223 | * backgroundMagenta color
224 | * @param text
225 | */
226 | export function backgroundMagenta(text: string) {
227 | return `${_backgroundMagenta}${text}${_reset}`;
228 | }
229 |
230 | /**
231 | * backgroundCyan color
232 | * @param text
233 | */
234 | export function backgroundCyan(text: string) {
235 | return `${_backgroundCyan}${text}${_reset}`;
236 | }
237 |
238 | /**
239 | * backgroundWhite color
240 | * @param text
241 | */
242 | export function backgroundWhite(text: string) {
243 | return `${_backgroundWhite}${text}${_reset}`;
244 | }
245 |
246 | /**
247 | * backgroundBrightBlack color
248 | * @param text
249 | */
250 | export function backgroundBrightBlack(text: string) {
251 | return `${_backgroundBrightBlack}${text}${_reset}`;
252 | }
253 |
254 | /**
255 | * backgroundBrightRed color
256 | * @param text
257 | */
258 | export function backgroundBrightRed(text: string) {
259 | return `${_backgroundBrightRed}${text}${_reset}`;
260 | }
261 |
262 | /**
263 | * backgroundBrightGreen color
264 | * @param text
265 | */
266 | export function backgroundBrightGreen(text: string) {
267 | return `${_backgroundBrightGreen}${text}${_reset}`;
268 | }
269 |
270 | /**
271 | * backgroundBrightYellow color
272 | * @param text
273 | */
274 | export function backgroundBrightYellow(text: string) {
275 | return `${_backgroundBrightYellow}${text}${_reset}`;
276 | }
277 |
278 | /**
279 | * backgroundBrightBlue color
280 | * @param text
281 | */
282 | export function backgroundBrightBlue(text: string) {
283 | return `${_backgroundBrightBlue}${text}${_reset}`;
284 | }
285 |
286 | /**
287 | * backgroundBrightMagenta color
288 | * @param text
289 | */
290 | export function backgroundBrightMagenta(text: string) {
291 | return `${_backgroundBrightMagenta}${text}${_reset}`;
292 | }
293 |
294 | /**
295 | * backgroundBrightCyan color
296 | * @param text
297 | */
298 | export function backgroundBrightCyan(text: string) {
299 | return `${_backgroundBrightCyan}${text}${_reset}`;
300 | }
301 |
302 | /**
303 | * backgroundBrightWhite color
304 | * @param text
305 | */
306 | export function backgroundBrightWhite(text: string) {
307 | return `${_backgroundBrightWhite}${text}${_reset}`;
308 | }
309 |
310 | /**
311 | * bold text
312 | * @param text
313 | */
314 | export function bold(text: string) {
315 | return `${_bold}${text}${_reset}`;
316 | }
317 |
318 | /**
319 | * underline text
320 | * @param text
321 | */
322 | export function underline(text: string) {
323 | return `${_underline}${text}${_reset}`;
324 | }
325 |
326 | /**
327 | * reverse color text
328 | * @param text
329 | */
330 | export function reversed(text: string) {
331 | return `${_reversed}${text}${_reset}`;
332 | }
333 |
334 | /**
335 | * ANSI 256 color text
336 | * @param text
337 | */
338 | export function color256(text: string, code: number) {
339 | if (code < 0 || code > 255) {
340 | console.log(
341 | red(
342 | bold(
343 | "only numbers between 0 and 255 can be passed in the color256 functio"
344 | )
345 | )
346 | );
347 | throw new Error();
348 | }
349 |
350 | return `\u001b[38;5;${code}m${text}${_reset}`;
351 | }
352 |
--------------------------------------------------------------------------------
/std/fs/exist.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) elsa land 2020.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | */
8 |
--------------------------------------------------------------------------------
/std/hash/aes.ts:
--------------------------------------------------------------------------------
1 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2 | /* AES implementation in JavaScript (c) Chris Veness 2005-2016 */
3 | /* MIT Licence */
4 | /* www.movable-type.co.uk/scripts/aes.html */
5 | /* - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - */
6 |
7 | /**
8 | * Copyright (c) elsa land 2020.
9 | *
10 | * This source code is licensed under the MIT license found in the
11 | * LICENSE file in the root directory of this source tree.
12 | *
13 | */
14 |
15 | /**
16 | * number of valid bits
17 | */
18 | type BitSize = "128" | "192" | "256";
19 |
20 | /**
21 | * implementation of AES ( Advanced Encryption Standard ) in TypeScript
22 | */
23 |
24 | export class AES {
25 | private static sBox = [
26 | 0x63,
27 | 0x7c,
28 | 0x77,
29 | 0x7b,
30 | 0xf2,
31 | 0x6b,
32 | 0x6f,
33 | 0xc5,
34 | 0x30,
35 | 0x01,
36 | 0x67,
37 | 0x2b,
38 | 0xfe,
39 | 0xd7,
40 | 0xab,
41 | 0x76,
42 | 0xca,
43 | 0x82,
44 | 0xc9,
45 | 0x7d,
46 | 0xfa,
47 | 0x59,
48 | 0x47,
49 | 0xf0,
50 | 0xad,
51 | 0xd4,
52 | 0xa2,
53 | 0xaf,
54 | 0x9c,
55 | 0xa4,
56 | 0x72,
57 | 0xc0,
58 | 0xb7,
59 | 0xfd,
60 | 0x93,
61 | 0x26,
62 | 0x36,
63 | 0x3f,
64 | 0xf7,
65 | 0xcc,
66 | 0x34,
67 | 0xa5,
68 | 0xe5,
69 | 0xf1,
70 | 0x71,
71 | 0xd8,
72 | 0x31,
73 | 0x15,
74 | 0x04,
75 | 0xc7,
76 | 0x23,
77 | 0xc3,
78 | 0x18,
79 | 0x96,
80 | 0x05,
81 | 0x9a,
82 | 0x07,
83 | 0x12,
84 | 0x80,
85 | 0xe2,
86 | 0xeb,
87 | 0x27,
88 | 0xb2,
89 | 0x75,
90 | 0x09,
91 | 0x83,
92 | 0x2c,
93 | 0x1a,
94 | 0x1b,
95 | 0x6e,
96 | 0x5a,
97 | 0xa0,
98 | 0x52,
99 | 0x3b,
100 | 0xd6,
101 | 0xb3,
102 | 0x29,
103 | 0xe3,
104 | 0x2f,
105 | 0x84,
106 | 0x53,
107 | 0xd1,
108 | 0x00,
109 | 0xed,
110 | 0x20,
111 | 0xfc,
112 | 0xb1,
113 | 0x5b,
114 | 0x6a,
115 | 0xcb,
116 | 0xbe,
117 | 0x39,
118 | 0x4a,
119 | 0x4c,
120 | 0x58,
121 | 0xcf,
122 | 0xd0,
123 | 0xef,
124 | 0xaa,
125 | 0xfb,
126 | 0x43,
127 | 0x4d,
128 | 0x33,
129 | 0x85,
130 | 0x45,
131 | 0xf9,
132 | 0x02,
133 | 0x7f,
134 | 0x50,
135 | 0x3c,
136 | 0x9f,
137 | 0xa8,
138 | 0x51,
139 | 0xa3,
140 | 0x40,
141 | 0x8f,
142 | 0x92,
143 | 0x9d,
144 | 0x38,
145 | 0xf5,
146 | 0xbc,
147 | 0xb6,
148 | 0xda,
149 | 0x21,
150 | 0x10,
151 | 0xff,
152 | 0xf3,
153 | 0xd2,
154 | 0xcd,
155 | 0x0c,
156 | 0x13,
157 | 0xec,
158 | 0x5f,
159 | 0x97,
160 | 0x44,
161 | 0x17,
162 | 0xc4,
163 | 0xa7,
164 | 0x7e,
165 | 0x3d,
166 | 0x64,
167 | 0x5d,
168 | 0x19,
169 | 0x73,
170 | 0x60,
171 | 0x81,
172 | 0x4f,
173 | 0xdc,
174 | 0x22,
175 | 0x2a,
176 | 0x90,
177 | 0x88,
178 | 0x46,
179 | 0xee,
180 | 0xb8,
181 | 0x14,
182 | 0xde,
183 | 0x5e,
184 | 0x0b,
185 | 0xdb,
186 | 0xe0,
187 | 0x32,
188 | 0x3a,
189 | 0x0a,
190 | 0x49,
191 | 0x06,
192 | 0x24,
193 | 0x5c,
194 | 0xc2,
195 | 0xd3,
196 | 0xac,
197 | 0x62,
198 | 0x91,
199 | 0x95,
200 | 0xe4,
201 | 0x79,
202 | 0xe7,
203 | 0xc8,
204 | 0x37,
205 | 0x6d,
206 | 0x8d,
207 | 0xd5,
208 | 0x4e,
209 | 0xa9,
210 | 0x6c,
211 | 0x56,
212 | 0xf4,
213 | 0xea,
214 | 0x65,
215 | 0x7a,
216 | 0xae,
217 | 0x08,
218 | 0xba,
219 | 0x78,
220 | 0x25,
221 | 0x2e,
222 | 0x1c,
223 | 0xa6,
224 | 0xb4,
225 | 0xc6,
226 | 0xe8,
227 | 0xdd,
228 | 0x74,
229 | 0x1f,
230 | 0x4b,
231 | 0xbd,
232 | 0x8b,
233 | 0x8a,
234 | 0x70,
235 | 0x3e,
236 | 0xb5,
237 | 0x66,
238 | 0x48,
239 | 0x03,
240 | 0xf6,
241 | 0x0e,
242 | 0x61,
243 | 0x35,
244 | 0x57,
245 | 0xb9,
246 | 0x86,
247 | 0xc1,
248 | 0x1d,
249 | 0x9e,
250 | 0xe1,
251 | 0xf8,
252 | 0x98,
253 | 0x11,
254 | 0x69,
255 | 0xd9,
256 | 0x8e,
257 | 0x94,
258 | 0x9b,
259 | 0x1e,
260 | 0x87,
261 | 0xe9,
262 | 0xce,
263 | 0x55,
264 | 0x28,
265 | 0xdf,
266 | 0x8c,
267 | 0xa1,
268 | 0x89,
269 | 0x0d,
270 | 0xbf,
271 | 0xe6,
272 | 0x42,
273 | 0x68,
274 | 0x41,
275 | 0x99,
276 | 0x2d,
277 | 0x0f,
278 | 0xb0,
279 | 0x54,
280 | 0xbb,
281 | 0x16,
282 | ];
283 |
284 | private static rCon = [
285 | [0x00, 0x00, 0x00, 0x00],
286 | [0x01, 0x00, 0x00, 0x00],
287 | [0x02, 0x00, 0x00, 0x00],
288 | [0x04, 0x00, 0x00, 0x00],
289 | [0x08, 0x00, 0x00, 0x00],
290 | [0x10, 0x00, 0x00, 0x00],
291 | [0x20, 0x00, 0x00, 0x00],
292 | [0x40, 0x00, 0x00, 0x00],
293 | [0x80, 0x00, 0x00, 0x00],
294 | [0x1b, 0x00, 0x00, 0x00],
295 | [0x36, 0x00, 0x00, 0x00],
296 | ];
297 | private static cipher(input: any, w: any) {
298 | const Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
299 | const Nr = w.length / Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
300 |
301 | let state = [[], [], [], []] as any[][]; // initialise 4xNb byte-array 'state' with input [§3.4]
302 |
303 | for (let i = 0; i < 4 * Nb; i++) {
304 | state[i % 4][Math.floor(i / 4)] = input[i];
305 | }
306 |
307 | state = AES.addRoundKey(state, w, 0, Nb);
308 |
309 | for (let round = 1; round < Nr; round++) {
310 | state = AES.subBytes(state, Nb);
311 | state = AES.shiftRows(state, Nb);
312 | state = AES.mixColumns(state, Nb);
313 | state = AES.addRoundKey(state, w, round, Nb);
314 | }
315 |
316 | state = AES.subBytes(state, Nb);
317 | state = AES.shiftRows(state, Nb);
318 | state = AES.addRoundKey(state, w, Nr, Nb);
319 |
320 | const output = new Array(4 * Nb); // convert state to 1-d array before returning [§3.4]
321 | for (let i = 0; i < 4 * Nb; i++)
322 | output[i] = state[i % 4][Math.floor(i / 4)];
323 |
324 | return output;
325 | }
326 |
327 | private static keyExpansion(key: any) {
328 | const Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
329 | const Nk = key.length / 4; // key length (in words): 4/6/8 for 128/192/256-bit keys
330 | const Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
331 |
332 | const w = new Array(Nb * (Nr + 1));
333 | let temp = new Array(4);
334 |
335 | // initialize first Nk words of expanded key with cipher key
336 | for (let i = 0; i < Nk; i++) {
337 | const r = [key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]];
338 | w[i] = r;
339 | }
340 |
341 | // expand the key into the remainder of the schedule
342 | for (let i = Nk; i < Nb * (Nr + 1); i++) {
343 | w[i] = new Array(4);
344 | for (let t = 0; t < 4; t++) {
345 | temp[t] = w[i - 1][t];
346 | }
347 | // each Nk'th word has extra transformation
348 | if (i % Nk == 0) {
349 | temp = AES.subWord(AES.rotWord(temp));
350 | for (let t = 0; t < 4; t++) {
351 | temp[t] ^= AES.rCon[i / Nk][t];
352 | }
353 | }
354 | // 256-bit key has subWord applied every 4th word
355 | else if (Nk > 6 && i % Nk == 4) {
356 | temp = AES.subWord(temp);
357 | }
358 | // xor w[i] with w[i-1] and w[i-Nk]
359 | for (let t = 0; t < 4; t++) {
360 | w[i][t] = w[i - Nk][t] ^ temp[t];
361 | }
362 | }
363 |
364 | return w;
365 | }
366 |
367 | private static subBytes(s: any, Nb: any) {
368 | for (let r = 0; r < 4; r++) {
369 | for (let c = 0; c < Nb; c++) {
370 | s[r][c] = AES.sBox[s[r][c]];
371 | }
372 | }
373 | return s;
374 | }
375 |
376 | private static shiftRows(s: any, Nb: any) {
377 | const t = new Array(4);
378 | for (let r = 1; r < 4; r++) {
379 | for (let c = 0; c < 4; c++) {
380 | t[c] = s[r][(c + r) % Nb];
381 | } // shift into temp copy
382 | for (let c = 0; c < 4; c++) {
383 | s[r][c] = t[c];
384 | } // and copy back
385 | } // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
386 | return s; // see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
387 | }
388 |
389 | private static mixColumns(s: any) {
390 | for (let c = 0; c < 4; c++) {
391 | const a = new Array(4); // 'a' is a copy of the current column from 's'
392 | const b = new Array(4); // 'b' is a•{02} in GF(2^8)
393 | for (let i = 0; i < 4; i++) {
394 | a[i] = s[i][c];
395 | b[i] = s[i][c] & 0x80 ? (s[i][c] << 1) ^ 0x011b : s[i][c] << 1;
396 | }
397 | // a[n] ^ b[n] is a•{03} in GF(2^8)
398 | s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // {02}•a0 + {03}•a1 + a2 + a3
399 | s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 • {02}•a1 + {03}•a2 + a3
400 | s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + {02}•a2 + {03}•a3
401 | s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // {03}•a0 + a1 + a2 + {02}•a3
402 | }
403 | return s;
404 | }
405 |
406 | private static addRoundKey(state: any, w: any, rnd: any, Nb: any) {
407 | for (let r = 0; r < 4; r++) {
408 | for (let c = 0; c < Nb; c++) {
409 | state[r][c] ^= w[rnd * 4 + c][r];
410 | }
411 | }
412 | return state;
413 | }
414 |
415 | private static subWord(w: any[]) {
416 | for (let i = 0; i < 4; i++) {
417 | w[i] = AES.sBox[w[i]];
418 | }
419 | return w;
420 | }
421 |
422 | private static rotWord(w: any[]) {
423 | const tmp = w[0];
424 | for (let i = 0; i < 3; i++) {
425 | w[i] = w[i + 1];
426 | }
427 | w[3] = tmp;
428 | return w;
429 | }
430 |
431 | private static base64Decode(text: string) {
432 | return atob(text);
433 | }
434 |
435 | private static base64Encode(text: string) {
436 | return btoa(text);
437 | }
438 |
439 | private static utf8Encode(text: string) {
440 | return unescape(encodeURIComponent(text));
441 | }
442 |
443 | private static utf8Decode(text: string) {
444 | try {
445 | return decodeURIComponent(escape(text));
446 | } catch (err) {
447 | return text;
448 | }
449 | }
450 |
451 | public static get Ctr() {
452 | return {
453 | /**
454 | *
455 | * @param ciphertext - encrypted text
456 | * @param password - encryption password
457 | * @param nBits - bits size
458 | *```ts
459 | * const Text = AES.Ctr.decrypt("uAIyZlqGe18XRt2+akj3wzCKbhtcINuC3RItd0U=", "elsa land", "128");
460 | *
461 | * console.log(Text); // hello world from elsa
462 | * ```
463 | */
464 | decrypt(ciphertext: string, password: string, nBits: BitSize) {
465 | const blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
466 |
467 | if (
468 | !(
469 | parseInt(nBits) === 128 ||
470 | parseInt(nBits) === 192 ||
471 | parseInt(nBits) === 256
472 | )
473 | ) {
474 | throw new Error("Key size is not 128 / 192 / 256");
475 | }
476 |
477 | ciphertext = AES.base64Decode(ciphertext);
478 | password = AES.utf8Encode(password);
479 |
480 | // use AES to encrypt password (mirroring encrypt routine)
481 | const nBytes = parseInt(nBits) / 8; // no bytes in key
482 | const pwBytes = new Array(nBytes);
483 | for (let i = 0; i < nBytes; i++) {
484 | pwBytes[i] = i < password.length ? password.charCodeAt(i) : 0;
485 | }
486 | let key = AES.cipher(pwBytes, AES.keyExpansion(pwBytes));
487 | key = key.concat(key.slice(0, nBytes - 16)); // expand key to 16/24/32 bytes long
488 |
489 | // recover nonce from 1st 8 bytes of ciphertext
490 | const counterBlock = new Array(8);
491 | const ctrTxt = ciphertext.slice(0, 8);
492 | for (let i = 0; i < 8; i++) {
493 | counterBlock[i] = ctrTxt.charCodeAt(i);
494 | }
495 |
496 | // generate key schedule
497 | const keySchedule = AES.keyExpansion(key);
498 |
499 | // separate ciphertext into blocks (skipping past initial 8 bytes)
500 | const nBlocks = Math.ceil((ciphertext.length - 8) / blockSize);
501 | const ct = new Array(nBlocks);
502 | for (let b = 0; b < nBlocks; b++)
503 | ct[b] = ciphertext.slice(
504 | 8 + b * blockSize,
505 | 8 + b * blockSize + blockSize
506 | );
507 | ((ciphertext as unknown) as any[]) = ct; // ciphertext is now array of block-length strings
508 |
509 | // plaintext will get generated block-by-block into array of block-length strings
510 | let plaintext = "";
511 |
512 | for (let b = 0; b < nBlocks; b++) {
513 | // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
514 | for (let c = 0; c < 4; c++) {
515 | counterBlock[15 - c] = (b >>> (c * 8)) & 0xff;
516 | }
517 | for (let c = 0; c < 4; c++) {
518 | counterBlock[15 - c - 4] =
519 | (((b + 1) / 0x100000000 - 1) >>> (c * 8)) & 0xff;
520 | }
521 |
522 | const cipherCntr = AES.cipher(counterBlock, keySchedule); // encrypt counter block
523 |
524 | const plaintxtByte = new Array(ciphertext[b].length);
525 | for (let i = 0; i < ciphertext[b].length; i++) {
526 | // -- xor plaintext with ciphered counter byte-by-byte --
527 | plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i);
528 | plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]);
529 | }
530 | plaintext += plaintxtByte.join("");
531 |
532 | // if within web worker, announce progress every 1000 blocks (roughly every 50ms)
533 | }
534 |
535 | plaintext = AES.utf8Decode(plaintext); // decode from UTF8 back to Unicode multi-byte chars
536 |
537 | return plaintext;
538 | },
539 |
540 | /**
541 | *
542 | * @param plaintext - text to encrypt
543 | * @param password - encryption password
544 | * @param nBits - bits size
545 | *```ts
546 | * const Text = AES.Ctr.decrypt("hello world from elsa", "elsa land", "128");
547 | *
548 | * console.log(Text); // uAIyZlqGe18XRt2+akj3wzCKbhtcINuC3RItd0U=
549 | * ```
550 | */
551 | encrypt(plaintext: string, password: string, nBits: BitSize) {
552 | const blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
553 | if (
554 | !(
555 | parseInt(nBits) === 128 ||
556 | parseInt(nBits) === 192 ||
557 | parseInt(nBits) === 256
558 | )
559 | ) {
560 | throw new Error("Key size is not 128 / 192 / 256");
561 | }
562 | plaintext = AES.utf8Encode(plaintext);
563 | password = AES.utf8Encode(password);
564 |
565 | // use AES itself to encrypt password to get cipher key (using plain password as source for key
566 | // expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use)
567 | const nBytes = parseInt(nBits) / 8; // no bytes in key (16/24/32)
568 | const pwBytes = new Array(nBytes);
569 | for (let i = 0; i < nBytes; i++) {
570 | // use 1st 16/24/32 chars of password for key
571 | pwBytes[i] = i < password.length ? password.charCodeAt(i) : 0;
572 | }
573 | let key = AES.cipher(pwBytes, AES.keyExpansion(pwBytes)); // gives us 16-byte key
574 | key = key.concat(key.slice(0, nBytes - 16)); // expand key to 16/24/32 bytes long
575 |
576 | // initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec,
577 | // [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106
578 | const counterBlock = new Array(blockSize);
579 |
580 | const nonce = new Date().getTime(); // timestamp: milliseconds since 1-Jan-1970
581 | const nonceMs = nonce % 1000;
582 | const nonceSec = Math.floor(nonce / 1000);
583 | const nonceRnd = Math.floor(Math.random() * 0xffff);
584 | // for debugging: nonce = nonceMs = nonceSec = nonceRnd = 0;
585 |
586 | for (let i = 0; i < 2; i++) {
587 | counterBlock[i] = (nonceMs >>> (i * 8)) & 0xff;
588 | }
589 | for (let i = 0; i < 2; i++) {
590 | counterBlock[i + 2] = (nonceRnd >>> (i * 8)) & 0xff;
591 | }
592 | for (let i = 0; i < 4; i++) {
593 | counterBlock[i + 4] = (nonceSec >>> (i * 8)) & 0xff;
594 | }
595 |
596 | // and convert it to a string to go on the front of the ciphertext
597 | let ctrTxt = "";
598 | for (let i = 0; i < 8; i++) {
599 | ctrTxt += String.fromCharCode(counterBlock[i]);
600 | }
601 |
602 | // generate key schedule - an expansion of the key into distinct Key Rounds for each round
603 | const keySchedule = AES.keyExpansion(key);
604 |
605 | const blockCount = Math.ceil(plaintext.length / blockSize);
606 | let ciphertext = "";
607 |
608 | for (let b = 0; b < blockCount; b++) {
609 | // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
610 | // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
611 | for (let c = 0; c < 4; c++) {
612 | counterBlock[15 - c] = (b >>> (c * 8)) & 0xff;
613 | }
614 | for (let c = 0; c < 4; c++) {
615 | counterBlock[15 - c - 4] = (b / 0x100000000) >>> (c * 8);
616 | }
617 |
618 | const cipherCntr = AES.cipher(counterBlock, keySchedule); // -- encrypt counter block --
619 |
620 | // block size is reduced on final block
621 | const blockLength =
622 | b < blockCount - 1
623 | ? blockSize
624 | : ((plaintext.length - 1) % blockSize) + 1;
625 | const cipherChar = new Array(blockLength);
626 |
627 | for (let i = 0; i < blockLength; i++) {
628 | // -- xor plaintext with ciphered counter char-by-char --
629 | cipherChar[i] =
630 | cipherCntr[i] ^ plaintext.charCodeAt(b * blockSize + i);
631 | cipherChar[i] = String.fromCharCode(cipherChar[i]);
632 | }
633 | ciphertext += cipherChar.join("");
634 |
635 | // if within web worker, announce progress every 1000 blocks (roughly every 50ms)
636 | }
637 |
638 | ciphertext = AES.base64Encode(ctrTxt + ciphertext);
639 |
640 | return ciphertext;
641 | },
642 | };
643 | }
644 | }
645 |
--------------------------------------------------------------------------------
/std/hash/hasher.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 |
3 | export type Message = string | ArrayBuffer;
4 | export type OutputFormat = "hex" | "base64";
5 |
6 | export interface Hasher {
7 | update(data: Message): this;
8 | digest(): ArrayBuffer;
9 | toString(format?: OutputFormat): string;
10 | }
11 |
--------------------------------------------------------------------------------
/std/hash/md5.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 |
3 | import * as hex from "../encoding/hex.ts";
4 |
5 | const TYPE_ERROR_MSG = "md5: `data` is invalid type";
6 | const BLOCK_SIZE = 64;
7 |
8 | export type Message = string | ArrayBuffer;
9 | /** Md5 hash */
10 | export class Md5 {
11 | #a: number;
12 | #b: number;
13 | #c: number;
14 | #d: number;
15 | #block: Uint8Array;
16 | #pos: number;
17 | #n0: number;
18 | #n1: number;
19 |
20 | constructor() {
21 | this.#a = 0x67452301;
22 | this.#b = 0xefcdab89;
23 | this.#c = 0x98badcfe;
24 | this.#d = 0x10325476;
25 | this.#block = new Uint8Array(BLOCK_SIZE);
26 | this.#pos = 0;
27 | this.#n0 = 0;
28 | this.#n1 = 0;
29 | }
30 |
31 | private addLength(len: number): void {
32 | let n0 = this.#n0;
33 | n0 += len;
34 | if (n0 > 0xffffffff) this.#n1 += 1;
35 | this.#n0 = n0 >>> 0;
36 | }
37 |
38 | private hash(block: Uint8Array): void {
39 | let a = this.#a;
40 | let b = this.#b;
41 | let c = this.#c;
42 | let d = this.#d;
43 |
44 | const blk = (i: number): number =>
45 | block[i] |
46 | (block[i + 1] << 8) |
47 | (block[i + 2] << 16) |
48 | (block[i + 3] << 24);
49 |
50 | const rol32 = (x: number, n: number): number => (x << n) | (x >>> (32 - n));
51 |
52 | const x0 = blk(0);
53 | const x1 = blk(4);
54 | const x2 = blk(8);
55 | const x3 = blk(12);
56 | const x4 = blk(16);
57 | const x5 = blk(20);
58 | const x6 = blk(24);
59 | const x7 = blk(28);
60 | const x8 = blk(32);
61 | const x9 = blk(36);
62 | const xa = blk(40);
63 | const xb = blk(44);
64 | const xc = blk(48);
65 | const xd = blk(52);
66 | const xe = blk(56);
67 | const xf = blk(60);
68 |
69 | // round 1
70 | a = b + rol32((((c ^ d) & b) ^ d) + a + x0 + 0xd76aa478, 7);
71 | d = a + rol32((((b ^ c) & a) ^ c) + d + x1 + 0xe8c7b756, 12);
72 | c = d + rol32((((a ^ b) & d) ^ b) + c + x2 + 0x242070db, 17);
73 | b = c + rol32((((d ^ a) & c) ^ a) + b + x3 + 0xc1bdceee, 22);
74 | a = b + rol32((((c ^ d) & b) ^ d) + a + x4 + 0xf57c0faf, 7);
75 | d = a + rol32((((b ^ c) & a) ^ c) + d + x5 + 0x4787c62a, 12);
76 | c = d + rol32((((a ^ b) & d) ^ b) + c + x6 + 0xa8304613, 17);
77 | b = c + rol32((((d ^ a) & c) ^ a) + b + x7 + 0xfd469501, 22);
78 | a = b + rol32((((c ^ d) & b) ^ d) + a + x8 + 0x698098d8, 7);
79 | d = a + rol32((((b ^ c) & a) ^ c) + d + x9 + 0x8b44f7af, 12);
80 | c = d + rol32((((a ^ b) & d) ^ b) + c + xa + 0xffff5bb1, 17);
81 | b = c + rol32((((d ^ a) & c) ^ a) + b + xb + 0x895cd7be, 22);
82 | a = b + rol32((((c ^ d) & b) ^ d) + a + xc + 0x6b901122, 7);
83 | d = a + rol32((((b ^ c) & a) ^ c) + d + xd + 0xfd987193, 12);
84 | c = d + rol32((((a ^ b) & d) ^ b) + c + xe + 0xa679438e, 17);
85 | b = c + rol32((((d ^ a) & c) ^ a) + b + xf + 0x49b40821, 22);
86 |
87 | // round 2
88 | a = b + rol32((((b ^ c) & d) ^ c) + a + x1 + 0xf61e2562, 5);
89 | d = a + rol32((((a ^ b) & c) ^ b) + d + x6 + 0xc040b340, 9);
90 | c = d + rol32((((d ^ a) & b) ^ a) + c + xb + 0x265e5a51, 14);
91 | b = c + rol32((((c ^ d) & a) ^ d) + b + x0 + 0xe9b6c7aa, 20);
92 | a = b + rol32((((b ^ c) & d) ^ c) + a + x5 + 0xd62f105d, 5);
93 | d = a + rol32((((a ^ b) & c) ^ b) + d + xa + 0x02441453, 9);
94 | c = d + rol32((((d ^ a) & b) ^ a) + c + xf + 0xd8a1e681, 14);
95 | b = c + rol32((((c ^ d) & a) ^ d) + b + x4 + 0xe7d3fbc8, 20);
96 | a = b + rol32((((b ^ c) & d) ^ c) + a + x9 + 0x21e1cde6, 5);
97 | d = a + rol32((((a ^ b) & c) ^ b) + d + xe + 0xc33707d6, 9);
98 | c = d + rol32((((d ^ a) & b) ^ a) + c + x3 + 0xf4d50d87, 14);
99 | b = c + rol32((((c ^ d) & a) ^ d) + b + x8 + 0x455a14ed, 20);
100 | a = b + rol32((((b ^ c) & d) ^ c) + a + xd + 0xa9e3e905, 5);
101 | d = a + rol32((((a ^ b) & c) ^ b) + d + x2 + 0xfcefa3f8, 9);
102 | c = d + rol32((((d ^ a) & b) ^ a) + c + x7 + 0x676f02d9, 14);
103 | b = c + rol32((((c ^ d) & a) ^ d) + b + xc + 0x8d2a4c8a, 20);
104 |
105 | // round 3
106 | a = b + rol32((b ^ c ^ d) + a + x5 + 0xfffa3942, 4);
107 | d = a + rol32((a ^ b ^ c) + d + x8 + 0x8771f681, 11);
108 | c = d + rol32((d ^ a ^ b) + c + xb + 0x6d9d6122, 16);
109 | b = c + rol32((c ^ d ^ a) + b + xe + 0xfde5380c, 23);
110 | a = b + rol32((b ^ c ^ d) + a + x1 + 0xa4beea44, 4);
111 | d = a + rol32((a ^ b ^ c) + d + x4 + 0x4bdecfa9, 11);
112 | c = d + rol32((d ^ a ^ b) + c + x7 + 0xf6bb4b60, 16);
113 | b = c + rol32((c ^ d ^ a) + b + xa + 0xbebfbc70, 23);
114 | a = b + rol32((b ^ c ^ d) + a + xd + 0x289b7ec6, 4);
115 | d = a + rol32((a ^ b ^ c) + d + x0 + 0xeaa127fa, 11);
116 | c = d + rol32((d ^ a ^ b) + c + x3 + 0xd4ef3085, 16);
117 | b = c + rol32((c ^ d ^ a) + b + x6 + 0x04881d05, 23);
118 | a = b + rol32((b ^ c ^ d) + a + x9 + 0xd9d4d039, 4);
119 | d = a + rol32((a ^ b ^ c) + d + xc + 0xe6db99e5, 11);
120 | c = d + rol32((d ^ a ^ b) + c + xf + 0x1fa27cf8, 16);
121 | b = c + rol32((c ^ d ^ a) + b + x2 + 0xc4ac5665, 23);
122 |
123 | // round 4
124 | a = b + rol32((c ^ (b | ~d)) + a + x0 + 0xf4292244, 6);
125 | d = a + rol32((b ^ (a | ~c)) + d + x7 + 0x432aff97, 10);
126 | c = d + rol32((a ^ (d | ~b)) + c + xe + 0xab9423a7, 15);
127 | b = c + rol32((d ^ (c | ~a)) + b + x5 + 0xfc93a039, 21);
128 | a = b + rol32((c ^ (b | ~d)) + a + xc + 0x655b59c3, 6);
129 | d = a + rol32((b ^ (a | ~c)) + d + x3 + 0x8f0ccc92, 10);
130 | c = d + rol32((a ^ (d | ~b)) + c + xa + 0xffeff47d, 15);
131 | b = c + rol32((d ^ (c | ~a)) + b + x1 + 0x85845dd1, 21);
132 | a = b + rol32((c ^ (b | ~d)) + a + x8 + 0x6fa87e4f, 6);
133 | d = a + rol32((b ^ (a | ~c)) + d + xf + 0xfe2ce6e0, 10);
134 | c = d + rol32((a ^ (d | ~b)) + c + x6 + 0xa3014314, 15);
135 | b = c + rol32((d ^ (c | ~a)) + b + xd + 0x4e0811a1, 21);
136 | a = b + rol32((c ^ (b | ~d)) + a + x4 + 0xf7537e82, 6);
137 | d = a + rol32((b ^ (a | ~c)) + d + xb + 0xbd3af235, 10);
138 | c = d + rol32((a ^ (d | ~b)) + c + x2 + 0x2ad7d2bb, 15);
139 | b = c + rol32((d ^ (c | ~a)) + b + x9 + 0xeb86d391, 21);
140 |
141 | this.#a = (this.#a + a) >>> 0;
142 | this.#b = (this.#b + b) >>> 0;
143 | this.#c = (this.#c + c) >>> 0;
144 | this.#d = (this.#d + d) >>> 0;
145 | }
146 |
147 | /**
148 | * Update internal state
149 | * @param data data to update, data cannot exceed 2^32 bytes
150 | */
151 | update(data: Message): this {
152 | let msg: Uint8Array;
153 |
154 | if (typeof data === "string") {
155 | msg = new TextEncoder().encode(data as string);
156 | } else if (typeof data === "object") {
157 | if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
158 | msg = new Uint8Array(data);
159 | } else {
160 | throw new Error(TYPE_ERROR_MSG);
161 | }
162 | } else {
163 | throw new Error(TYPE_ERROR_MSG);
164 | }
165 |
166 | let pos = this.#pos;
167 | const free = BLOCK_SIZE - pos;
168 |
169 | if (msg.length < free) {
170 | this.#block.set(msg, pos);
171 | pos += msg.length;
172 | } else {
173 | // hash first block
174 | this.#block.set(msg.slice(0, free), pos);
175 | this.hash(this.#block);
176 |
177 | // hash as many blocks as possible
178 | let i = free;
179 | while (i + BLOCK_SIZE <= msg.length) {
180 | this.hash(msg.slice(i, i + BLOCK_SIZE));
181 | i += BLOCK_SIZE;
182 | }
183 |
184 | // store leftover
185 | this.#block.fill(0).set(msg.slice(i), 0);
186 | pos = msg.length - i;
187 | }
188 |
189 | this.#pos = pos;
190 | this.addLength(msg.length);
191 |
192 | return this;
193 | }
194 |
195 | /** Returns final hash */
196 | digest(): ArrayBuffer {
197 | let padLen = BLOCK_SIZE - this.#pos;
198 | if (padLen < 9) padLen += BLOCK_SIZE;
199 |
200 | const pad = new Uint8Array(padLen);
201 |
202 | pad[0] = 0x80;
203 |
204 | const n0 = this.#n0 << 3;
205 | const n1 = (this.#n1 << 3) | (this.#n0 >>> 29);
206 | pad[pad.length - 8] = n0 & 0xff;
207 | pad[pad.length - 7] = (n0 >>> 8) & 0xff;
208 | pad[pad.length - 6] = (n0 >>> 16) & 0xff;
209 | pad[pad.length - 5] = (n0 >>> 24) & 0xff;
210 | pad[pad.length - 4] = n1 & 0xff;
211 | pad[pad.length - 3] = (n1 >>> 8) & 0xff;
212 | pad[pad.length - 2] = (n1 >>> 16) & 0xff;
213 | pad[pad.length - 1] = (n1 >>> 24) & 0xff;
214 |
215 | this.update(pad.buffer);
216 |
217 | const hash = new ArrayBuffer(16);
218 | const hashView = new DataView(hash);
219 | hashView.setUint32(0, this.#a, true);
220 | hashView.setUint32(4, this.#b, true);
221 | hashView.setUint32(8, this.#c, true);
222 | hashView.setUint32(12, this.#d, true);
223 |
224 | return hash;
225 | }
226 |
227 | /**
228 | * Returns hash as a string of given format
229 | * @param format format of output string (hex or base64). Default is hex
230 | */
231 | toString(format: "hex" | "base64" = "hex"): string {
232 | const hash = this.digest();
233 |
234 | switch (format) {
235 | case "hex":
236 | return hex.encodeToString(new Uint8Array(hash));
237 | case "base64": {
238 | const data = new Uint8Array(hash);
239 | let dataString = "";
240 | for (let i = 0; i < data.length; ++i) {
241 | dataString += String.fromCharCode(data[i]);
242 | }
243 | return btoa(dataString);
244 | }
245 | default:
246 | throw new Error("md5: invalid format");
247 | }
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/std/hash/sha1.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | /*
3 | * [js-sha1]{@link https://github.com/emn178/js-sha1}
4 | *
5 | * @version 0.6.0
6 | * @author Chen, Yi-Cyuan [emn178@gmail.com]
7 | * @copyright Chen, Yi-Cyuan 2014-2017
8 | * @license MIT
9 | */
10 |
11 | export type Message = string | number[] | ArrayBuffer;
12 |
13 | const HEX_CHARS = "0123456789abcdef".split("");
14 | const EXTRA = [-2147483648, 8388608, 32768, 128] as const;
15 | const SHIFT = [24, 16, 8, 0] as const;
16 |
17 | const blocks: number[] = [];
18 |
19 | export class Sha1 {
20 | #blocks!: number[];
21 | #block!: number;
22 | #start!: number;
23 | #bytes!: number;
24 | #hBytes!: number;
25 | #finalized!: boolean;
26 | #hashed!: boolean;
27 |
28 | #h0 = 0x67452301;
29 | #h1 = 0xefcdab89;
30 | #h2 = 0x98badcfe;
31 | #h3 = 0x10325476;
32 | #h4 = 0xc3d2e1f0;
33 | #lastByteIndex = 0;
34 |
35 | constructor(sharedMemory = false) {
36 | if (sharedMemory) {
37 | // deno-fmt-ignore
38 | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
39 | this.#blocks = blocks;
40 | } else {
41 | this.#blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
42 | }
43 |
44 | this.#h0 = 0x67452301;
45 | this.#h1 = 0xefcdab89;
46 | this.#h2 = 0x98badcfe;
47 | this.#h3 = 0x10325476;
48 | this.#h4 = 0xc3d2e1f0;
49 |
50 | this.#block = this.#start = this.#bytes = this.#hBytes = 0;
51 | this.#finalized = this.#hashed = false;
52 | }
53 |
54 | update(message: Message): this {
55 | if (this.#finalized) {
56 | return this;
57 | }
58 |
59 | let msg: string | number[] | Uint8Array | undefined;
60 | if (message instanceof ArrayBuffer) {
61 | msg = new Uint8Array(message);
62 | } else {
63 | msg = message;
64 | }
65 |
66 | let index = 0;
67 | const length = msg.length;
68 | const blocks = this.#blocks;
69 |
70 | while (index < length) {
71 | let i: number;
72 | if (this.#hashed) {
73 | this.#hashed = false;
74 | blocks[0] = this.#block;
75 | // deno-fmt-ignore
76 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
77 | }
78 |
79 | if (typeof msg !== "string") {
80 | for (i = this.#start; index < length && i < 64; ++index) {
81 | blocks[i >> 2] |= msg[index] << SHIFT[i++ & 3];
82 | }
83 | } else {
84 | for (i = this.#start; index < length && i < 64; ++index) {
85 | let code = msg.charCodeAt(index);
86 | if (code < 0x80) {
87 | blocks[i >> 2] |= code << SHIFT[i++ & 3];
88 | } else if (code < 0x800) {
89 | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
90 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
91 | } else if (code < 0xd800 || code >= 0xe000) {
92 | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
93 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
94 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
95 | } else {
96 | code =
97 | 0x10000 +
98 | (((code & 0x3ff) << 10) | (msg.charCodeAt(++index) & 0x3ff));
99 | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
100 | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
101 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
102 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
103 | }
104 | }
105 | }
106 |
107 | this.#lastByteIndex = i;
108 | this.#bytes += i - this.#start;
109 | if (i >= 64) {
110 | this.#block = blocks[16];
111 | this.#start = i - 64;
112 | this.hash();
113 | this.#hashed = true;
114 | } else {
115 | this.#start = i;
116 | }
117 | }
118 | if (this.#bytes > 4294967295) {
119 | this.#hBytes += (this.#bytes / 4294967296) >>> 0;
120 | this.#bytes = this.#bytes >>> 0;
121 | }
122 | return this;
123 | }
124 |
125 | private finalize(): void {
126 | if (this.#finalized) {
127 | return;
128 | }
129 | this.#finalized = true;
130 | const blocks = this.#blocks;
131 | const i = this.#lastByteIndex;
132 | blocks[16] = this.#block;
133 | blocks[i >> 2] |= EXTRA[i & 3];
134 | this.#block = blocks[16];
135 | if (i >= 56) {
136 | if (!this.#hashed) {
137 | this.hash();
138 | }
139 | blocks[0] = this.#block;
140 | // deno-fmt-ignore
141 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
142 | }
143 | blocks[14] = (this.#hBytes << 3) | (this.#bytes >>> 29);
144 | blocks[15] = this.#bytes << 3;
145 | this.hash();
146 | }
147 |
148 | private hash(): void {
149 | let a = this.#h0;
150 | let b = this.#h1;
151 | let c = this.#h2;
152 | let d = this.#h3;
153 | let e = this.#h4;
154 | let f: number;
155 | let j: number;
156 | let t: number;
157 | const blocks = this.#blocks;
158 |
159 | for (j = 16; j < 80; ++j) {
160 | t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
161 | blocks[j] = (t << 1) | (t >>> 31);
162 | }
163 |
164 | for (j = 0; j < 20; j += 5) {
165 | f = (b & c) | (~b & d);
166 | t = (a << 5) | (a >>> 27);
167 | e = (t + f + e + 1518500249 + blocks[j]) >>> 0;
168 | b = (b << 30) | (b >>> 2);
169 |
170 | f = (a & b) | (~a & c);
171 | t = (e << 5) | (e >>> 27);
172 | d = (t + f + d + 1518500249 + blocks[j + 1]) >>> 0;
173 | a = (a << 30) | (a >>> 2);
174 |
175 | f = (e & a) | (~e & b);
176 | t = (d << 5) | (d >>> 27);
177 | c = (t + f + c + 1518500249 + blocks[j + 2]) >>> 0;
178 | e = (e << 30) | (e >>> 2);
179 |
180 | f = (d & e) | (~d & a);
181 | t = (c << 5) | (c >>> 27);
182 | b = (t + f + b + 1518500249 + blocks[j + 3]) >>> 0;
183 | d = (d << 30) | (d >>> 2);
184 |
185 | f = (c & d) | (~c & e);
186 | t = (b << 5) | (b >>> 27);
187 | a = (t + f + a + 1518500249 + blocks[j + 4]) >>> 0;
188 | c = (c << 30) | (c >>> 2);
189 | }
190 |
191 | for (; j < 40; j += 5) {
192 | f = b ^ c ^ d;
193 | t = (a << 5) | (a >>> 27);
194 | e = (t + f + e + 1859775393 + blocks[j]) >>> 0;
195 | b = (b << 30) | (b >>> 2);
196 |
197 | f = a ^ b ^ c;
198 | t = (e << 5) | (e >>> 27);
199 | d = (t + f + d + 1859775393 + blocks[j + 1]) >>> 0;
200 | a = (a << 30) | (a >>> 2);
201 |
202 | f = e ^ a ^ b;
203 | t = (d << 5) | (d >>> 27);
204 | c = (t + f + c + 1859775393 + blocks[j + 2]) >>> 0;
205 | e = (e << 30) | (e >>> 2);
206 |
207 | f = d ^ e ^ a;
208 | t = (c << 5) | (c >>> 27);
209 | b = (t + f + b + 1859775393 + blocks[j + 3]) >>> 0;
210 | d = (d << 30) | (d >>> 2);
211 |
212 | f = c ^ d ^ e;
213 | t = (b << 5) | (b >>> 27);
214 | a = (t + f + a + 1859775393 + blocks[j + 4]) >>> 0;
215 | c = (c << 30) | (c >>> 2);
216 | }
217 |
218 | for (; j < 60; j += 5) {
219 | f = (b & c) | (b & d) | (c & d);
220 | t = (a << 5) | (a >>> 27);
221 | e = (t + f + e - 1894007588 + blocks[j]) >>> 0;
222 | b = (b << 30) | (b >>> 2);
223 |
224 | f = (a & b) | (a & c) | (b & c);
225 | t = (e << 5) | (e >>> 27);
226 | d = (t + f + d - 1894007588 + blocks[j + 1]) >>> 0;
227 | a = (a << 30) | (a >>> 2);
228 |
229 | f = (e & a) | (e & b) | (a & b);
230 | t = (d << 5) | (d >>> 27);
231 | c = (t + f + c - 1894007588 + blocks[j + 2]) >>> 0;
232 | e = (e << 30) | (e >>> 2);
233 |
234 | f = (d & e) | (d & a) | (e & a);
235 | t = (c << 5) | (c >>> 27);
236 | b = (t + f + b - 1894007588 + blocks[j + 3]) >>> 0;
237 | d = (d << 30) | (d >>> 2);
238 |
239 | f = (c & d) | (c & e) | (d & e);
240 | t = (b << 5) | (b >>> 27);
241 | a = (t + f + a - 1894007588 + blocks[j + 4]) >>> 0;
242 | c = (c << 30) | (c >>> 2);
243 | }
244 |
245 | for (; j < 80; j += 5) {
246 | f = b ^ c ^ d;
247 | t = (a << 5) | (a >>> 27);
248 | e = (t + f + e - 899497514 + blocks[j]) >>> 0;
249 | b = (b << 30) | (b >>> 2);
250 |
251 | f = a ^ b ^ c;
252 | t = (e << 5) | (e >>> 27);
253 | d = (t + f + d - 899497514 + blocks[j + 1]) >>> 0;
254 | a = (a << 30) | (a >>> 2);
255 |
256 | f = e ^ a ^ b;
257 | t = (d << 5) | (d >>> 27);
258 | c = (t + f + c - 899497514 + blocks[j + 2]) >>> 0;
259 | e = (e << 30) | (e >>> 2);
260 |
261 | f = d ^ e ^ a;
262 | t = (c << 5) | (c >>> 27);
263 | b = (t + f + b - 899497514 + blocks[j + 3]) >>> 0;
264 | d = (d << 30) | (d >>> 2);
265 |
266 | f = c ^ d ^ e;
267 | t = (b << 5) | (b >>> 27);
268 | a = (t + f + a - 899497514 + blocks[j + 4]) >>> 0;
269 | c = (c << 30) | (c >>> 2);
270 | }
271 |
272 | this.#h0 = (this.#h0 + a) >>> 0;
273 | this.#h1 = (this.#h1 + b) >>> 0;
274 | this.#h2 = (this.#h2 + c) >>> 0;
275 | this.#h3 = (this.#h3 + d) >>> 0;
276 | this.#h4 = (this.#h4 + e) >>> 0;
277 | }
278 |
279 | hex(): string {
280 | this.finalize();
281 |
282 | const h0 = this.#h0;
283 | const h1 = this.#h1;
284 | const h2 = this.#h2;
285 | const h3 = this.#h3;
286 | const h4 = this.#h4;
287 |
288 | return (
289 | HEX_CHARS[(h0 >> 28) & 0x0f] +
290 | HEX_CHARS[(h0 >> 24) & 0x0f] +
291 | HEX_CHARS[(h0 >> 20) & 0x0f] +
292 | HEX_CHARS[(h0 >> 16) & 0x0f] +
293 | HEX_CHARS[(h0 >> 12) & 0x0f] +
294 | HEX_CHARS[(h0 >> 8) & 0x0f] +
295 | HEX_CHARS[(h0 >> 4) & 0x0f] +
296 | HEX_CHARS[h0 & 0x0f] +
297 | HEX_CHARS[(h1 >> 28) & 0x0f] +
298 | HEX_CHARS[(h1 >> 24) & 0x0f] +
299 | HEX_CHARS[(h1 >> 20) & 0x0f] +
300 | HEX_CHARS[(h1 >> 16) & 0x0f] +
301 | HEX_CHARS[(h1 >> 12) & 0x0f] +
302 | HEX_CHARS[(h1 >> 8) & 0x0f] +
303 | HEX_CHARS[(h1 >> 4) & 0x0f] +
304 | HEX_CHARS[h1 & 0x0f] +
305 | HEX_CHARS[(h2 >> 28) & 0x0f] +
306 | HEX_CHARS[(h2 >> 24) & 0x0f] +
307 | HEX_CHARS[(h2 >> 20) & 0x0f] +
308 | HEX_CHARS[(h2 >> 16) & 0x0f] +
309 | HEX_CHARS[(h2 >> 12) & 0x0f] +
310 | HEX_CHARS[(h2 >> 8) & 0x0f] +
311 | HEX_CHARS[(h2 >> 4) & 0x0f] +
312 | HEX_CHARS[h2 & 0x0f] +
313 | HEX_CHARS[(h3 >> 28) & 0x0f] +
314 | HEX_CHARS[(h3 >> 24) & 0x0f] +
315 | HEX_CHARS[(h3 >> 20) & 0x0f] +
316 | HEX_CHARS[(h3 >> 16) & 0x0f] +
317 | HEX_CHARS[(h3 >> 12) & 0x0f] +
318 | HEX_CHARS[(h3 >> 8) & 0x0f] +
319 | HEX_CHARS[(h3 >> 4) & 0x0f] +
320 | HEX_CHARS[h3 & 0x0f] +
321 | HEX_CHARS[(h4 >> 28) & 0x0f] +
322 | HEX_CHARS[(h4 >> 24) & 0x0f] +
323 | HEX_CHARS[(h4 >> 20) & 0x0f] +
324 | HEX_CHARS[(h4 >> 16) & 0x0f] +
325 | HEX_CHARS[(h4 >> 12) & 0x0f] +
326 | HEX_CHARS[(h4 >> 8) & 0x0f] +
327 | HEX_CHARS[(h4 >> 4) & 0x0f] +
328 | HEX_CHARS[h4 & 0x0f]
329 | );
330 | }
331 |
332 | toString(): string {
333 | return this.hex();
334 | }
335 |
336 | digest(): number[] {
337 | this.finalize();
338 |
339 | const h0 = this.#h0;
340 | const h1 = this.#h1;
341 | const h2 = this.#h2;
342 | const h3 = this.#h3;
343 | const h4 = this.#h4;
344 |
345 | return [
346 | (h0 >> 24) & 0xff,
347 | (h0 >> 16) & 0xff,
348 | (h0 >> 8) & 0xff,
349 | h0 & 0xff,
350 | (h1 >> 24) & 0xff,
351 | (h1 >> 16) & 0xff,
352 | (h1 >> 8) & 0xff,
353 | h1 & 0xff,
354 | (h2 >> 24) & 0xff,
355 | (h2 >> 16) & 0xff,
356 | (h2 >> 8) & 0xff,
357 | h2 & 0xff,
358 | (h3 >> 24) & 0xff,
359 | (h3 >> 16) & 0xff,
360 | (h3 >> 8) & 0xff,
361 | h3 & 0xff,
362 | (h4 >> 24) & 0xff,
363 | (h4 >> 16) & 0xff,
364 | (h4 >> 8) & 0xff,
365 | h4 & 0xff,
366 | ];
367 | }
368 |
369 | array(): number[] {
370 | return this.digest();
371 | }
372 |
373 | arrayBuffer(): ArrayBuffer {
374 | this.finalize();
375 |
376 | const buffer = new ArrayBuffer(20);
377 | const dataView = new DataView(buffer);
378 | dataView.setUint32(0, this.#h0);
379 | dataView.setUint32(4, this.#h1);
380 | dataView.setUint32(8, this.#h2);
381 | dataView.setUint32(12, this.#h3);
382 | dataView.setUint32(16, this.#h4);
383 |
384 | return buffer;
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/std/hash/sha256.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | /*
3 | * Adapted to deno from:
4 | *
5 | * [js-sha256]{@link https://github.com/emn178/js-sha256}
6 | *
7 | * @version 0.9.0
8 | * @author Chen, Yi-Cyuan [emn178@gmail.com]
9 | * @copyright Chen, Yi-Cyuan 2014-2017
10 | * @license MIT
11 | */
12 |
13 | export type Message = string | number[] | ArrayBuffer;
14 |
15 | const HEX_CHARS = "0123456789abcdef".split("");
16 | const EXTRA = [-2147483648, 8388608, 32768, 128] as const;
17 | const SHIFT = [24, 16, 8, 0] as const;
18 | // deno-fmt-ignore
19 | const K = [
20 | 0x428a2f98,
21 | 0x71374491,
22 | 0xb5c0fbcf,
23 | 0xe9b5dba5,
24 | 0x3956c25b,
25 | 0x59f111f1,
26 | 0x923f82a4,
27 | 0xab1c5ed5,
28 | 0xd807aa98,
29 | 0x12835b01,
30 | 0x243185be,
31 | 0x550c7dc3,
32 | 0x72be5d74,
33 | 0x80deb1fe,
34 | 0x9bdc06a7,
35 | 0xc19bf174,
36 | 0xe49b69c1,
37 | 0xefbe4786,
38 | 0x0fc19dc6,
39 | 0x240ca1cc,
40 | 0x2de92c6f,
41 | 0x4a7484aa,
42 | 0x5cb0a9dc,
43 | 0x76f988da,
44 | 0x983e5152,
45 | 0xa831c66d,
46 | 0xb00327c8,
47 | 0xbf597fc7,
48 | 0xc6e00bf3,
49 | 0xd5a79147,
50 | 0x06ca6351,
51 | 0x14292967,
52 | 0x27b70a85,
53 | 0x2e1b2138,
54 | 0x4d2c6dfc,
55 | 0x53380d13,
56 | 0x650a7354,
57 | 0x766a0abb,
58 | 0x81c2c92e,
59 | 0x92722c85,
60 | 0xa2bfe8a1,
61 | 0xa81a664b,
62 | 0xc24b8b70,
63 | 0xc76c51a3,
64 | 0xd192e819,
65 | 0xd6990624,
66 | 0xf40e3585,
67 | 0x106aa070,
68 | 0x19a4c116,
69 | 0x1e376c08,
70 | 0x2748774c,
71 | 0x34b0bcb5,
72 | 0x391c0cb3,
73 | 0x4ed8aa4a,
74 | 0x5b9cca4f,
75 | 0x682e6ff3,
76 | 0x748f82ee,
77 | 0x78a5636f,
78 | 0x84c87814,
79 | 0x8cc70208,
80 | 0x90befffa,
81 | 0xa4506ceb,
82 | 0xbef9a3f7,
83 | 0xc67178f2,
84 | ] as const;
85 |
86 | const blocks: number[] = [];
87 |
88 | export class Sha256 {
89 | #block!: number;
90 | #blocks!: number[];
91 | #bytes!: number;
92 | #finalized!: boolean;
93 | #first!: boolean;
94 | #h0!: number;
95 | #h1!: number;
96 | #h2!: number;
97 | #h3!: number;
98 | #h4!: number;
99 | #h5!: number;
100 | #h6!: number;
101 | #h7!: number;
102 | #hashed!: boolean;
103 | #hBytes!: number;
104 | #is224!: boolean;
105 | #lastByteIndex = 0;
106 | #start!: number;
107 |
108 | constructor(is224 = false, sharedMemory = false) {
109 | this.init(is224, sharedMemory);
110 | }
111 |
112 | protected init(is224: boolean, sharedMemory: boolean): void {
113 | if (sharedMemory) {
114 | // deno-fmt-ignore
115 | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
116 | this.#blocks = blocks;
117 | } else {
118 | this.#blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
119 | }
120 |
121 | if (is224) {
122 | this.#h0 = 0xc1059ed8;
123 | this.#h1 = 0x367cd507;
124 | this.#h2 = 0x3070dd17;
125 | this.#h3 = 0xf70e5939;
126 | this.#h4 = 0xffc00b31;
127 | this.#h5 = 0x68581511;
128 | this.#h6 = 0x64f98fa7;
129 | this.#h7 = 0xbefa4fa4;
130 | } else {
131 | // 256
132 | this.#h0 = 0x6a09e667;
133 | this.#h1 = 0xbb67ae85;
134 | this.#h2 = 0x3c6ef372;
135 | this.#h3 = 0xa54ff53a;
136 | this.#h4 = 0x510e527f;
137 | this.#h5 = 0x9b05688c;
138 | this.#h6 = 0x1f83d9ab;
139 | this.#h7 = 0x5be0cd19;
140 | }
141 |
142 | this.#block = this.#start = this.#bytes = this.#hBytes = 0;
143 | this.#finalized = this.#hashed = false;
144 | this.#first = true;
145 | this.#is224 = is224;
146 | }
147 |
148 | /** Update hash
149 | *
150 | * @param message The message you want to hash.
151 | */
152 | update(message: Message): this {
153 | if (this.#finalized) {
154 | return this;
155 | }
156 |
157 | let msg: string | number[] | Uint8Array | undefined;
158 | if (message instanceof ArrayBuffer) {
159 | msg = new Uint8Array(message);
160 | } else {
161 | msg = message;
162 | }
163 |
164 | let index = 0;
165 | const length = msg.length;
166 | const blocks = this.#blocks;
167 |
168 | while (index < length) {
169 | let i: number;
170 | if (this.#hashed) {
171 | this.#hashed = false;
172 | blocks[0] = this.#block;
173 | // deno-fmt-ignore
174 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
175 | }
176 |
177 | if (typeof msg !== "string") {
178 | for (i = this.#start; index < length && i < 64; ++index) {
179 | blocks[i >> 2] |= msg[index] << SHIFT[i++ & 3];
180 | }
181 | } else {
182 | for (i = this.#start; index < length && i < 64; ++index) {
183 | let code = msg.charCodeAt(index);
184 | if (code < 0x80) {
185 | blocks[i >> 2] |= code << SHIFT[i++ & 3];
186 | } else if (code < 0x800) {
187 | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
188 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
189 | } else if (code < 0xd800 || code >= 0xe000) {
190 | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
191 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
192 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
193 | } else {
194 | code =
195 | 0x10000 +
196 | (((code & 0x3ff) << 10) | (msg.charCodeAt(++index) & 0x3ff));
197 | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
198 | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
199 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
200 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
201 | }
202 | }
203 | }
204 |
205 | this.#lastByteIndex = i;
206 | this.#bytes += i - this.#start;
207 | if (i >= 64) {
208 | this.#block = blocks[16];
209 | this.#start = i - 64;
210 | this.hash();
211 | this.#hashed = true;
212 | } else {
213 | this.#start = i;
214 | }
215 | }
216 | if (this.#bytes > 4294967295) {
217 | this.#hBytes += (this.#bytes / 4294967296) << 0;
218 | this.#bytes = this.#bytes % 4294967296;
219 | }
220 | return this;
221 | }
222 |
223 | protected finalize(): void {
224 | if (this.#finalized) {
225 | return;
226 | }
227 | this.#finalized = true;
228 | const blocks = this.#blocks;
229 | const i = this.#lastByteIndex;
230 | blocks[16] = this.#block;
231 | blocks[i >> 2] |= EXTRA[i & 3];
232 | this.#block = blocks[16];
233 | if (i >= 56) {
234 | if (!this.#hashed) {
235 | this.hash();
236 | }
237 | blocks[0] = this.#block;
238 | // deno-fmt-ignore
239 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
240 | }
241 | blocks[14] = (this.#hBytes << 3) | (this.#bytes >>> 29);
242 | blocks[15] = this.#bytes << 3;
243 | this.hash();
244 | }
245 |
246 | protected hash(): void {
247 | let a = this.#h0;
248 | let b = this.#h1;
249 | let c = this.#h2;
250 | let d = this.#h3;
251 | let e = this.#h4;
252 | let f = this.#h5;
253 | let g = this.#h6;
254 | let h = this.#h7;
255 | const blocks = this.#blocks;
256 | let s0: number;
257 | let s1: number;
258 | let maj: number;
259 | let t1: number;
260 | let t2: number;
261 | let ch: number;
262 | let ab: number;
263 | let da: number;
264 | let cd: number;
265 | let bc: number;
266 |
267 | for (let j = 16; j < 64; ++j) {
268 | // rightrotate
269 | t1 = blocks[j - 15];
270 | s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3);
271 | t1 = blocks[j - 2];
272 | s1 =
273 | ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10);
274 | blocks[j] = (blocks[j - 16] + s0 + blocks[j - 7] + s1) << 0;
275 | }
276 |
277 | bc = b & c;
278 | for (let j = 0; j < 64; j += 4) {
279 | if (this.#first) {
280 | if (this.#is224) {
281 | ab = 300032;
282 | t1 = blocks[0] - 1413257819;
283 | h = (t1 - 150054599) << 0;
284 | d = (t1 + 24177077) << 0;
285 | } else {
286 | ab = 704751109;
287 | t1 = blocks[0] - 210244248;
288 | h = (t1 - 1521486534) << 0;
289 | d = (t1 + 143694565) << 0;
290 | }
291 | this.#first = false;
292 | } else {
293 | s0 =
294 | ((a >>> 2) | (a << 30)) ^
295 | ((a >>> 13) | (a << 19)) ^
296 | ((a >>> 22) | (a << 10));
297 | s1 =
298 | ((e >>> 6) | (e << 26)) ^
299 | ((e >>> 11) | (e << 21)) ^
300 | ((e >>> 25) | (e << 7));
301 | ab = a & b;
302 | maj = ab ^ (a & c) ^ bc;
303 | ch = (e & f) ^ (~e & g);
304 | t1 = h + s1 + ch + K[j] + blocks[j];
305 | t2 = s0 + maj;
306 | h = (d + t1) << 0;
307 | d = (t1 + t2) << 0;
308 | }
309 | s0 =
310 | ((d >>> 2) | (d << 30)) ^
311 | ((d >>> 13) | (d << 19)) ^
312 | ((d >>> 22) | (d << 10));
313 | s1 =
314 | ((h >>> 6) | (h << 26)) ^
315 | ((h >>> 11) | (h << 21)) ^
316 | ((h >>> 25) | (h << 7));
317 | da = d & a;
318 | maj = da ^ (d & b) ^ ab;
319 | ch = (h & e) ^ (~h & f);
320 | t1 = g + s1 + ch + K[j + 1] + blocks[j + 1];
321 | t2 = s0 + maj;
322 | g = (c + t1) << 0;
323 | c = (t1 + t2) << 0;
324 | s0 =
325 | ((c >>> 2) | (c << 30)) ^
326 | ((c >>> 13) | (c << 19)) ^
327 | ((c >>> 22) | (c << 10));
328 | s1 =
329 | ((g >>> 6) | (g << 26)) ^
330 | ((g >>> 11) | (g << 21)) ^
331 | ((g >>> 25) | (g << 7));
332 | cd = c & d;
333 | maj = cd ^ (c & a) ^ da;
334 | ch = (g & h) ^ (~g & e);
335 | t1 = f + s1 + ch + K[j + 2] + blocks[j + 2];
336 | t2 = s0 + maj;
337 | f = (b + t1) << 0;
338 | b = (t1 + t2) << 0;
339 | s0 =
340 | ((b >>> 2) | (b << 30)) ^
341 | ((b >>> 13) | (b << 19)) ^
342 | ((b >>> 22) | (b << 10));
343 | s1 =
344 | ((f >>> 6) | (f << 26)) ^
345 | ((f >>> 11) | (f << 21)) ^
346 | ((f >>> 25) | (f << 7));
347 | bc = b & c;
348 | maj = bc ^ (b & d) ^ cd;
349 | ch = (f & g) ^ (~f & h);
350 | t1 = e + s1 + ch + K[j + 3] + blocks[j + 3];
351 | t2 = s0 + maj;
352 | e = (a + t1) << 0;
353 | a = (t1 + t2) << 0;
354 | }
355 |
356 | this.#h0 = (this.#h0 + a) << 0;
357 | this.#h1 = (this.#h1 + b) << 0;
358 | this.#h2 = (this.#h2 + c) << 0;
359 | this.#h3 = (this.#h3 + d) << 0;
360 | this.#h4 = (this.#h4 + e) << 0;
361 | this.#h5 = (this.#h5 + f) << 0;
362 | this.#h6 = (this.#h6 + g) << 0;
363 | this.#h7 = (this.#h7 + h) << 0;
364 | }
365 |
366 | /** Return hash in hex string. */
367 | hex(): string {
368 | this.finalize();
369 |
370 | const h0 = this.#h0;
371 | const h1 = this.#h1;
372 | const h2 = this.#h2;
373 | const h3 = this.#h3;
374 | const h4 = this.#h4;
375 | const h5 = this.#h5;
376 | const h6 = this.#h6;
377 | const h7 = this.#h7;
378 |
379 | let hex =
380 | HEX_CHARS[(h0 >> 28) & 0x0f] +
381 | HEX_CHARS[(h0 >> 24) & 0x0f] +
382 | HEX_CHARS[(h0 >> 20) & 0x0f] +
383 | HEX_CHARS[(h0 >> 16) & 0x0f] +
384 | HEX_CHARS[(h0 >> 12) & 0x0f] +
385 | HEX_CHARS[(h0 >> 8) & 0x0f] +
386 | HEX_CHARS[(h0 >> 4) & 0x0f] +
387 | HEX_CHARS[h0 & 0x0f] +
388 | HEX_CHARS[(h1 >> 28) & 0x0f] +
389 | HEX_CHARS[(h1 >> 24) & 0x0f] +
390 | HEX_CHARS[(h1 >> 20) & 0x0f] +
391 | HEX_CHARS[(h1 >> 16) & 0x0f] +
392 | HEX_CHARS[(h1 >> 12) & 0x0f] +
393 | HEX_CHARS[(h1 >> 8) & 0x0f] +
394 | HEX_CHARS[(h1 >> 4) & 0x0f] +
395 | HEX_CHARS[h1 & 0x0f] +
396 | HEX_CHARS[(h2 >> 28) & 0x0f] +
397 | HEX_CHARS[(h2 >> 24) & 0x0f] +
398 | HEX_CHARS[(h2 >> 20) & 0x0f] +
399 | HEX_CHARS[(h2 >> 16) & 0x0f] +
400 | HEX_CHARS[(h2 >> 12) & 0x0f] +
401 | HEX_CHARS[(h2 >> 8) & 0x0f] +
402 | HEX_CHARS[(h2 >> 4) & 0x0f] +
403 | HEX_CHARS[h2 & 0x0f] +
404 | HEX_CHARS[(h3 >> 28) & 0x0f] +
405 | HEX_CHARS[(h3 >> 24) & 0x0f] +
406 | HEX_CHARS[(h3 >> 20) & 0x0f] +
407 | HEX_CHARS[(h3 >> 16) & 0x0f] +
408 | HEX_CHARS[(h3 >> 12) & 0x0f] +
409 | HEX_CHARS[(h3 >> 8) & 0x0f] +
410 | HEX_CHARS[(h3 >> 4) & 0x0f] +
411 | HEX_CHARS[h3 & 0x0f] +
412 | HEX_CHARS[(h4 >> 28) & 0x0f] +
413 | HEX_CHARS[(h4 >> 24) & 0x0f] +
414 | HEX_CHARS[(h4 >> 20) & 0x0f] +
415 | HEX_CHARS[(h4 >> 16) & 0x0f] +
416 | HEX_CHARS[(h4 >> 12) & 0x0f] +
417 | HEX_CHARS[(h4 >> 8) & 0x0f] +
418 | HEX_CHARS[(h4 >> 4) & 0x0f] +
419 | HEX_CHARS[h4 & 0x0f] +
420 | HEX_CHARS[(h5 >> 28) & 0x0f] +
421 | HEX_CHARS[(h5 >> 24) & 0x0f] +
422 | HEX_CHARS[(h5 >> 20) & 0x0f] +
423 | HEX_CHARS[(h5 >> 16) & 0x0f] +
424 | HEX_CHARS[(h5 >> 12) & 0x0f] +
425 | HEX_CHARS[(h5 >> 8) & 0x0f] +
426 | HEX_CHARS[(h5 >> 4) & 0x0f] +
427 | HEX_CHARS[h5 & 0x0f] +
428 | HEX_CHARS[(h6 >> 28) & 0x0f] +
429 | HEX_CHARS[(h6 >> 24) & 0x0f] +
430 | HEX_CHARS[(h6 >> 20) & 0x0f] +
431 | HEX_CHARS[(h6 >> 16) & 0x0f] +
432 | HEX_CHARS[(h6 >> 12) & 0x0f] +
433 | HEX_CHARS[(h6 >> 8) & 0x0f] +
434 | HEX_CHARS[(h6 >> 4) & 0x0f] +
435 | HEX_CHARS[h6 & 0x0f];
436 | if (!this.#is224) {
437 | hex +=
438 | HEX_CHARS[(h7 >> 28) & 0x0f] +
439 | HEX_CHARS[(h7 >> 24) & 0x0f] +
440 | HEX_CHARS[(h7 >> 20) & 0x0f] +
441 | HEX_CHARS[(h7 >> 16) & 0x0f] +
442 | HEX_CHARS[(h7 >> 12) & 0x0f] +
443 | HEX_CHARS[(h7 >> 8) & 0x0f] +
444 | HEX_CHARS[(h7 >> 4) & 0x0f] +
445 | HEX_CHARS[h7 & 0x0f];
446 | }
447 | return hex;
448 | }
449 |
450 | /** Return hash in hex string. */
451 | toString(): string {
452 | return this.hex();
453 | }
454 |
455 | /** Return hash in integer array. */
456 | digest(): number[] {
457 | this.finalize();
458 |
459 | const h0 = this.#h0;
460 | const h1 = this.#h1;
461 | const h2 = this.#h2;
462 | const h3 = this.#h3;
463 | const h4 = this.#h4;
464 | const h5 = this.#h5;
465 | const h6 = this.#h6;
466 | const h7 = this.#h7;
467 |
468 | const arr = [
469 | (h0 >> 24) & 0xff,
470 | (h0 >> 16) & 0xff,
471 | (h0 >> 8) & 0xff,
472 | h0 & 0xff,
473 | (h1 >> 24) & 0xff,
474 | (h1 >> 16) & 0xff,
475 | (h1 >> 8) & 0xff,
476 | h1 & 0xff,
477 | (h2 >> 24) & 0xff,
478 | (h2 >> 16) & 0xff,
479 | (h2 >> 8) & 0xff,
480 | h2 & 0xff,
481 | (h3 >> 24) & 0xff,
482 | (h3 >> 16) & 0xff,
483 | (h3 >> 8) & 0xff,
484 | h3 & 0xff,
485 | (h4 >> 24) & 0xff,
486 | (h4 >> 16) & 0xff,
487 | (h4 >> 8) & 0xff,
488 | h4 & 0xff,
489 | (h5 >> 24) & 0xff,
490 | (h5 >> 16) & 0xff,
491 | (h5 >> 8) & 0xff,
492 | h5 & 0xff,
493 | (h6 >> 24) & 0xff,
494 | (h6 >> 16) & 0xff,
495 | (h6 >> 8) & 0xff,
496 | h6 & 0xff,
497 | ];
498 | if (!this.#is224) {
499 | arr.push(
500 | (h7 >> 24) & 0xff,
501 | (h7 >> 16) & 0xff,
502 | (h7 >> 8) & 0xff,
503 | h7 & 0xff
504 | );
505 | }
506 | return arr;
507 | }
508 |
509 | /** Return hash in integer array. */
510 | array(): number[] {
511 | return this.digest();
512 | }
513 |
514 | /** Return hash in ArrayBuffer. */
515 | arrayBuffer(): ArrayBuffer {
516 | this.finalize();
517 |
518 | const buffer = new ArrayBuffer(this.#is224 ? 28 : 32);
519 | const dataView = new DataView(buffer);
520 | dataView.setUint32(0, this.#h0);
521 | dataView.setUint32(4, this.#h1);
522 | dataView.setUint32(8, this.#h2);
523 | dataView.setUint32(12, this.#h3);
524 | dataView.setUint32(16, this.#h4);
525 | dataView.setUint32(20, this.#h5);
526 | dataView.setUint32(24, this.#h6);
527 | if (!this.#is224) {
528 | dataView.setUint32(28, this.#h7);
529 | }
530 | return buffer;
531 | }
532 | }
533 |
534 | export class HmacSha256 extends Sha256 {
535 | #inner: boolean;
536 | #is224: boolean;
537 | #oKeyPad: number[];
538 | #sharedMemory: boolean;
539 |
540 | constructor(secretKey: Message, is224 = false, sharedMemory = false) {
541 | super(is224, sharedMemory);
542 |
543 | let key: number[] | Uint8Array | undefined;
544 | if (typeof secretKey === "string") {
545 | const bytes: number[] = [];
546 | const length = secretKey.length;
547 | let index = 0;
548 | for (let i = 0; i < length; ++i) {
549 | let code = secretKey.charCodeAt(i);
550 | if (code < 0x80) {
551 | bytes[index++] = code;
552 | } else if (code < 0x800) {
553 | bytes[index++] = 0xc0 | (code >> 6);
554 | bytes[index++] = 0x80 | (code & 0x3f);
555 | } else if (code < 0xd800 || code >= 0xe000) {
556 | bytes[index++] = 0xe0 | (code >> 12);
557 | bytes[index++] = 0x80 | ((code >> 6) & 0x3f);
558 | bytes[index++] = 0x80 | (code & 0x3f);
559 | } else {
560 | code =
561 | 0x10000 +
562 | (((code & 0x3ff) << 10) | (secretKey.charCodeAt(++i) & 0x3ff));
563 | bytes[index++] = 0xf0 | (code >> 18);
564 | bytes[index++] = 0x80 | ((code >> 12) & 0x3f);
565 | bytes[index++] = 0x80 | ((code >> 6) & 0x3f);
566 | bytes[index++] = 0x80 | (code & 0x3f);
567 | }
568 | }
569 | key = bytes;
570 | } else {
571 | if (secretKey instanceof ArrayBuffer) {
572 | key = new Uint8Array(secretKey);
573 | } else {
574 | key = secretKey;
575 | }
576 | }
577 |
578 | if (key.length > 64) {
579 | key = new Sha256(is224, true).update(key).array();
580 | }
581 |
582 | const oKeyPad: number[] = [];
583 | const iKeyPad: number[] = [];
584 | for (let i = 0; i < 64; ++i) {
585 | const b = key[i] || 0;
586 | oKeyPad[i] = 0x5c ^ b;
587 | iKeyPad[i] = 0x36 ^ b;
588 | }
589 |
590 | this.update(iKeyPad);
591 | this.#oKeyPad = oKeyPad;
592 | this.#inner = true;
593 | this.#is224 = is224;
594 | this.#sharedMemory = sharedMemory;
595 | }
596 |
597 | protected finalize(): void {
598 | super.finalize();
599 | if (this.#inner) {
600 | this.#inner = false;
601 | const innerHash = this.array();
602 | super.init(this.#is224, this.#sharedMemory);
603 | this.update(this.#oKeyPad);
604 | this.update(innerHash);
605 | super.finalize();
606 | }
607 | }
608 | }
609 |
--------------------------------------------------------------------------------
/std/uuid/common.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | /**
3 | * Converts the byte array to a UUID string
4 | * @param bytes Used to convert Byte to Hex
5 | */
6 | export function bytesToUuid(bytes: number[] | Uint8Array): string {
7 | const bits: string[] = [...bytes].map((bit): string => {
8 | const s: string = bit.toString(16);
9 | return bit < 0x10 ? "0" + s : s;
10 | });
11 | return [
12 | ...bits.slice(0, 4),
13 | "-",
14 | ...bits.slice(4, 6),
15 | "-",
16 | ...bits.slice(6, 8),
17 | "-",
18 | ...bits.slice(8, 10),
19 | "-",
20 | ...bits.slice(10, 16),
21 | ].join("");
22 | }
23 | /**
24 | * Converts a string to a byte array by converting the hex value to a number
25 | * @param uuid Value that gets converted
26 | */
27 | export function uuidToBytes(uuid: string): number[] {
28 | const bytes: number[] = [];
29 |
30 | uuid.replace(/[a-fA-F0-9]{2}/g, (hex: string): string => {
31 | bytes.push(parseInt(hex, 16));
32 | return "";
33 | });
34 |
35 | return bytes;
36 | }
37 | /**
38 | * Converts a string to a byte array using the char code
39 | * @param str Value that gets converted
40 | */
41 | export function stringToBytes(str: string): number[] {
42 | str = unescape(encodeURIComponent(str));
43 | const bytes = new Array(str.length);
44 | for (let i = 0; i < str.length; i++) {
45 | bytes[i] = str.charCodeAt(i);
46 | }
47 | return bytes;
48 | }
49 | /**
50 | * Creates a buffer for creating a SHA-1 hash
51 | * @param content Buffer for SHA-1 hash
52 | */
53 | export function createBuffer(content: number[]): ArrayBuffer {
54 | const arrayBuffer = new ArrayBuffer(content.length);
55 | const uint8Array = new Uint8Array(arrayBuffer);
56 | for (let i = 0; i < content.length; i++) {
57 | uint8Array[i] = content[i];
58 | }
59 | return arrayBuffer;
60 | }
61 |
--------------------------------------------------------------------------------
/std/uuid/v1.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | import { bytesToUuid } from "./common.ts";
3 |
4 | const UUID_RE = new RegExp(
5 | "^[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
6 | "i"
7 | );
8 | /**
9 | * Validates the UUID v1
10 | * @param id UUID value
11 | */
12 | export function validate(id: string): boolean {
13 | return UUID_RE.test(id);
14 | }
15 |
16 | let _nodeId: number[];
17 | let _clockseq: number;
18 |
19 | let _lastMSecs = 0;
20 | let _lastNSecs = 0;
21 |
22 | type V1Options = {
23 | node?: number[];
24 | clockseq?: number;
25 | msecs?: number;
26 | nsecs?: number;
27 | random?: number[];
28 | rng?: () => number[];
29 | };
30 | /**
31 | * Generates a RFC4122 v1 UUID (time-based)
32 | * @param options Can use RFC time sequence values as overwrites
33 | * @param buf Can allow the UUID to be written in byte-form starting at the offset
34 | * @param offset Index to start writing on the UUID bytes in buffer
35 | */
36 | export function generate(
37 | options?: V1Options | null,
38 | buf?: number[],
39 | offset?: number
40 | ): string | number[] {
41 | let i = (buf && offset) || 0;
42 | const b = buf || [];
43 |
44 | options = options || {};
45 | let node = options.node || _nodeId;
46 | let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq;
47 |
48 | if (node == null || clockseq == null) {
49 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
50 | const seedBytes: any =
51 | options.random ||
52 | options.rng ||
53 | crypto.getRandomValues(new Uint8Array(16));
54 | if (node == null) {
55 | node = _nodeId = [
56 | seedBytes[0] | 0x01,
57 | seedBytes[1],
58 | seedBytes[2],
59 | seedBytes[3],
60 | seedBytes[4],
61 | seedBytes[5],
62 | ];
63 | }
64 | if (clockseq == null) {
65 | clockseq = _clockseq = ((seedBytes[6] << 8) | seedBytes[7]) & 0x3fff;
66 | }
67 | }
68 | let msecs =
69 | options.msecs !== undefined ? options.msecs : new Date().getTime();
70 |
71 | let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;
72 |
73 | const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000;
74 |
75 | if (dt < 0 && options.clockseq === undefined) {
76 | clockseq = (clockseq + 1) & 0x3fff;
77 | }
78 |
79 | if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
80 | nsecs = 0;
81 | }
82 |
83 | if (nsecs >= 10000) {
84 | throw new Error("Can't create more than 10M uuids/sec");
85 | }
86 |
87 | _lastMSecs = msecs;
88 | _lastNSecs = nsecs;
89 | _clockseq = clockseq;
90 |
91 | msecs += 12219292800000;
92 |
93 | const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
94 | b[i++] = (tl >>> 24) & 0xff;
95 | b[i++] = (tl >>> 16) & 0xff;
96 | b[i++] = (tl >>> 8) & 0xff;
97 | b[i++] = tl & 0xff;
98 |
99 | const tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff;
100 | b[i++] = (tmh >>> 8) & 0xff;
101 | b[i++] = tmh & 0xff;
102 |
103 | b[i++] = ((tmh >>> 24) & 0xf) | 0x10;
104 | b[i++] = (tmh >>> 16) & 0xff;
105 |
106 | b[i++] = (clockseq >>> 8) | 0x80;
107 |
108 | b[i++] = clockseq & 0xff;
109 |
110 | for (let n = 0; n < 6; ++n) {
111 | b[i + n] = node[n];
112 | }
113 |
114 | return buf ? buf : bytesToUuid(b);
115 | }
116 |
--------------------------------------------------------------------------------
/std/uuid/v4.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | import { bytesToUuid } from "./common.ts";
3 |
4 | const UUID_RE = new RegExp(
5 | "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
6 | "i"
7 | );
8 | /**
9 | * Validates the UUID v4
10 | * @param id UUID value
11 | */
12 | export function validate(id: string): boolean {
13 | return UUID_RE.test(id);
14 | }
15 | /** Generates a RFC4122 v4 UUID (pseudo-randomly-based) */
16 | export function generate(): string {
17 | const rnds = crypto.getRandomValues(new Uint8Array(16));
18 |
19 | rnds[6] = (rnds[6] & 0x0f) | 0x40; // Version 4
20 | rnds[8] = (rnds[8] & 0x3f) | 0x80; // Variant 10
21 |
22 | return bytesToUuid(rnds);
23 | }
24 |
--------------------------------------------------------------------------------
/std/uuid/v5.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | import {
3 | bytesToUuid,
4 | createBuffer,
5 | stringToBytes,
6 | uuidToBytes,
7 | } from "./common.ts";
8 | import { Sha1 } from "../hash/sha1.ts";
9 |
10 | const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11 | /**
12 | * Validates the UUID v5
13 | * @param id UUID value
14 | */
15 | export function validate(id: string): boolean {
16 | return UUID_RE.test(id);
17 | }
18 |
19 | interface V5Options {
20 | value: string | number[];
21 | namespace: string | number[];
22 | }
23 | /**
24 | * Generates a RFC4122 v5 UUID (SHA-1 namespace-based)
25 | * @param options Can use a namespace and value to creat SHA-1 hash
26 | * @param buf Can allow the UUID to be written in byte-form starting at the offset
27 | * @param offset Index to start writing on the UUID bytes in buffer
28 | */
29 | export function generate(
30 | options: V5Options,
31 | buf?: number[],
32 | offset?: number
33 | ): string | number[] {
34 | const i = (buf && offset) || 0;
35 |
36 | let { value, namespace } = options;
37 | if (typeof value == "string") {
38 | value = stringToBytes(value as string);
39 | }
40 |
41 | if (typeof namespace == "string") {
42 | namespace = uuidToBytes(namespace as string);
43 | }
44 |
45 | if (namespace.length === 16)
46 | throw new TypeError(
47 | "namespace must be uuid string or an Array of 16 byte values"
48 | );
49 |
50 | const content = (namespace as number[]).concat(value as number[]);
51 | const bytes = new Sha1().update(createBuffer(content)).digest();
52 |
53 | bytes[6] = (bytes[6] & 0x0f) | 0x50;
54 | bytes[8] = (bytes[8] & 0x3f) | 0x80;
55 |
56 | if (buf) {
57 | for (let idx = 0; idx < 16; ++idx) {
58 | buf[i + idx] = bytes[idx];
59 | }
60 | }
61 |
62 | return buf || bytesToUuid(bytes);
63 | }
64 |
--------------------------------------------------------------------------------
/testing/bench/fs.js:
--------------------------------------------------------------------------------
1 | Elsa.readFile("main.go");
2 |
--------------------------------------------------------------------------------
/testing/bench/fs_deno.js:
--------------------------------------------------------------------------------
1 | await Deno.readTextFile("main.go");
2 |
--------------------------------------------------------------------------------
/testing/bench/fs_node.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 |
3 | fs.readFile("main.go", "utf8", function (err) {
4 | if (err) throw err;
5 | });
6 |
--------------------------------------------------------------------------------
/testing/bench/http_deno.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2 | import { serve } from "https://deno.land/std@0.74.0/http/server.ts";
3 |
4 | const addr = Deno.args[0] || "127.0.0.1:4500";
5 | const server = serve(addr);
6 | const body = new TextEncoder().encode("Hello World");
7 |
8 | console.log(`http://${addr}/`);
9 | for await (const req of server) {
10 | const res = {
11 | body,
12 | headers: new Headers(),
13 | };
14 | req.respond(res).catch(() => {});
15 | }
16 |
--------------------------------------------------------------------------------
/testing/bench/http_elsa.ts:
--------------------------------------------------------------------------------
1 | const addr = Elsa.args[0] || "127.0.0.1:4501";
2 | const body = "Hello World";
3 |
4 | function handler() {
5 | const res = {
6 | body,
7 | status: 200,
8 | };
9 | return res;
10 | }
11 |
12 | console.log(`http://${addr}/`);
13 |
14 | Elsa.serve(addr, handler);
15 |
--------------------------------------------------------------------------------
/testing/bench/pi.js:
--------------------------------------------------------------------------------
1 | let i = 1n;
2 | let x = 3n * 10n ** 10020n;
3 | let pi = x;
4 |
5 | while (x > 0) {
6 | x = (x * i) / ((i + 1n) * 4n);
7 | pi += x / (i + 2n);
8 | i += 2n;
9 | }
10 |
11 | const dg = pi / 10n ** 20n;
12 | console.log(dg);
13 |
--------------------------------------------------------------------------------
/testing/bundle/basics.js:
--------------------------------------------------------------------------------
1 | function test() {
2 | return "test";
3 | }
4 |
5 | console.log("Elsa 1");
6 |
7 | console.log("Elsa 2");
8 |
9 | console.log("Elsa 3");
10 |
11 | console.log({ a: { b: 1 } });
12 |
13 | const arrow = () => {
14 | return 1;
15 | };
16 |
17 | arrow(); // 1
18 | test(); // "test"
19 |
--------------------------------------------------------------------------------
/testing/bundle/basics.js.mini.out:
--------------------------------------------------------------------------------
1 | (()=>{function o(){return"test"}console.log("Elsa 1");console.log("Elsa 2");console.log("Elsa 3");console.log({a:{b:1}});const l=()=>1;l();o();})();
2 |
--------------------------------------------------------------------------------
/testing/bundle/basics.js.out:
--------------------------------------------------------------------------------
1 | (() => {
2 | // bundle/basics.js
3 | function test() {
4 | return "test";
5 | }
6 | console.log("Elsa 1");
7 | console.log("Elsa 2");
8 | console.log("Elsa 3");
9 | console.log({a: {b: 1}});
10 | const arrow = () => {
11 | return 1;
12 | };
13 | arrow();
14 | test();
15 | })();
16 |
17 |
--------------------------------------------------------------------------------
/testing/bundle/export.js:
--------------------------------------------------------------------------------
1 | export default function () {
2 | return 1;
3 | }
4 |
5 | export function hello(name) {
6 | console.log(`Hello ${name}`);
7 | }
8 |
9 | export const Name = "Elsa";
10 |
--------------------------------------------------------------------------------
/testing/bundle/hello.ts:
--------------------------------------------------------------------------------
1 | var x: string = "variable";
2 | console.log(1);
3 |
--------------------------------------------------------------------------------
/testing/bundle/hello.ts.out:
--------------------------------------------------------------------------------
1 | (() => {
2 | // bundle/hello.ts
3 | console.log(1);
4 | })();
5 |
6 |
--------------------------------------------------------------------------------
/testing/bundle/local_imports.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elsaland/elsa/1982c3d1cad0a8ed4160b52d68c7c941445a8fa5/testing/bundle/local_imports.js
--------------------------------------------------------------------------------
/testing/bundle/local_imports.js.out:
--------------------------------------------------------------------------------
1 | (() => {})();
2 |
--------------------------------------------------------------------------------
/testing/bundle/served.ts:
--------------------------------------------------------------------------------
1 | export function hello(name: string) {
2 | console.log(name);
3 | }
4 |
--------------------------------------------------------------------------------
/testing/bundle/url.ts:
--------------------------------------------------------------------------------
1 | import { hello } from "http://localhost:8100/bundle/served.ts";
2 | hello("Elsa");
3 |
--------------------------------------------------------------------------------
/testing/bundle_test.go:
--------------------------------------------------------------------------------
1 | package testing
2 |
3 | import (
4 | "io/ioutil"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/elsaland/elsa/bundler"
9 | "github.com/elsaland/elsa/module"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | type bundleTestDesc struct {
14 | name string
15 | path string
16 | category string
17 | minify bool
18 | }
19 |
20 | var config = &module.Config{}
21 |
22 | var TestDesc = []bundleTestDesc{
23 | {
24 | "Bundle no-import js module",
25 | "bundle/basics.js",
26 | "vanilla",
27 | false,
28 | },
29 | {
30 | "Bundle local js module",
31 | "bundle/local_imports.js",
32 | "es",
33 | false,
34 | },
35 | {
36 | "Bundle no-import ts module",
37 | "bundle/hello.ts",
38 | "ts",
39 | false,
40 | },
41 | {
42 | "Bundle URL import",
43 | "bundle/url.ts",
44 | "url",
45 | false,
46 | },
47 | {
48 | "Bundle minified",
49 | "bundle/basics.js",
50 | "vanilla",
51 | true,
52 | },
53 | }
54 |
55 | // utility method to read the expected output for a particular test file
56 | func readOutData(sourceFile string, minified bool) string {
57 | path := sourceFile
58 | if minified {
59 | path += ".mini.out"
60 | } else {
61 | path += ".out"
62 | }
63 | dat, e := ioutil.ReadFile(path)
64 | if e != nil {
65 | panic(e)
66 | }
67 | return string(dat)
68 | }
69 |
70 | func TestBundle(t *testing.T) {
71 | // Start the local test server in a seperate goroutine.
72 | go StartTestServer()
73 | r := strings.NewReplacer("\n", "", "\r", "")
74 |
75 | for _, tst := range TestDesc {
76 | // Passing Test
77 | t.Run(tst.name, func(t *testing.T) {
78 | assert := assert.New(t)
79 | // Since URL bundle outputs may differ depending upon the if the import is cached or not
80 | // therefore, we only check whether it didn't exit with a bad status code.
81 | // TODO: we might want to do wildcard based assertion
82 | if tst.category == "url" {
83 | bundle := bundler.BundleModule(tst.path, false, config)
84 |
85 | assert.NotNil(bundle)
86 | } else {
87 | bundle := bundler.BundleModule(tst.path, tst.minify, config)
88 | expected := readOutData(tst.path, tst.minify)
89 |
90 | // Remove newlines from the out data and bundle and assert
91 | assert.Equal(r.Replace(bundle), r.Replace(expected))
92 | }
93 | })
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/testing/core_test.go:
--------------------------------------------------------------------------------
1 | package testing
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/elsaland/elsa/core"
7 | "github.com/elsaland/elsa/core/options"
8 | "github.com/elsaland/elsa/module"
9 | "github.com/elsaland/elsa/util"
10 | )
11 |
12 | type runTestDesc struct {
13 | name string
14 | source string
15 | }
16 |
17 | var RunTestDesc = []runTestDesc{
18 | {
19 | "Basic",
20 | "1 + 1",
21 | },
22 | {
23 | "Bundle local js module",
24 | "Elsa.readFile('fs/sample.txt')",
25 | },
26 | }
27 |
28 | func TestCore(t *testing.T) {
29 | // Load config
30 | config, err := module.ConfigLoad()
31 | util.Check(err)
32 | for _, tst := range RunTestDesc {
33 | // Passing Test
34 | t.Run(tst.name, func(t *testing.T) {
35 | // Run the test source with filename as test.js, default config and all perms
36 | env := options.Environment{
37 | NoColor: config.Options.NoColor,
38 | Args: []string{},
39 | }
40 | opt := options.Options{
41 | SourceFile: "test.js",
42 | Source: tst.source,
43 | Perms: &options.Perms{Fs: true},
44 | Env: env,
45 | }
46 | core.Run(opt)
47 | })
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/testing/exports/1.js:
--------------------------------------------------------------------------------
1 | export default function () {
2 | return 1;
3 | }
4 |
--------------------------------------------------------------------------------
/testing/exports/2.js:
--------------------------------------------------------------------------------
1 | export default function () {
2 | console.log(1);
3 | }
4 |
--------------------------------------------------------------------------------
/testing/exports/import.js:
--------------------------------------------------------------------------------
1 | import X from "./1.js";
2 | import Y from "./2.js";
3 |
4 | const __default2 = "test";
5 | console.warn(X, Y, __default2);
6 |
--------------------------------------------------------------------------------
/testing/fs/cwd_test.js:
--------------------------------------------------------------------------------
1 | console.log(Elsa.cwd());
2 |
--------------------------------------------------------------------------------
/testing/fs/exists_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test fs - exists (true)": function () {
3 | if (!Elsa.exists("testing/fs/sample.txt"))
4 | throw new Error("Elsa.exists failed");
5 | },
6 | "test fs - exists (false)": function () {
7 | if (Elsa.exists("testing/idonotexist.txt"))
8 | throw new Error("Elsa.exists failed");
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/testing/fs/readFile_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test fs - readFile": function () {
3 | Elsa.readFile("testing/fs/sample.txt");
4 | },
5 | });
6 |
--------------------------------------------------------------------------------
/testing/fs/remove_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test fs - remove": function () {
3 | Elsa.writeFile(
4 | "testing/fs/to_remove.txt",
5 | `
6 | I am written by Elsa.writeFile
7 | ...and then removed by Elsa.writeFile
8 | `
9 | );
10 |
11 | Elsa.remove("testing/fs/to_remove.txt");
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/testing/fs/sample.txt:
--------------------------------------------------------------------------------
1 | Sample file written using Elsa
--------------------------------------------------------------------------------
/testing/fs/stats_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test fs - stat": function () {
3 | Elsa.stat("testing/fs/sample.txt");
4 | },
5 | });
6 |
--------------------------------------------------------------------------------
/testing/fs/writeFile_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test fs - writeFile": function () {
3 | Elsa.writeFile("testing/fs/sample.txt", "Sample file written using Elsa");
4 | },
5 | });
6 |
--------------------------------------------------------------------------------
/testing/http/serve.js:
--------------------------------------------------------------------------------
1 | function handler(req) {
2 | console.log(req);
3 | console.log(`Method: ${req.Method}\nPath: ${req.Path}`);
4 | return { body: "Hello from Elsa :)", status: 200 };
5 | }
6 |
7 | Elsa.serve(":8080", handler);
8 |
--------------------------------------------------------------------------------
/testing/miscellaneous/args.js:
--------------------------------------------------------------------------------
1 | // WARNING! pkg_test will fail if you move this file.
2 | console.log(Elsa.args);
3 |
--------------------------------------------------------------------------------
/testing/miscellaneous/args_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test Elsa.args": function () {
3 | if (!Elsa.args || typeof Elsa.args == "undefined") {
4 | throw new Error("Elsa.args is undefined");
5 | }
6 | },
7 | });
8 |
--------------------------------------------------------------------------------
/testing/miscellaneous/mode_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test Elsa.mode": function () {
3 | if (!Elsa.mode || typeof Elsa.mode == "undefined") {
4 | throw new Error("Elsa.mode is undefined");
5 | }
6 | },
7 | });
8 |
--------------------------------------------------------------------------------
/testing/miscellaneous/while.js:
--------------------------------------------------------------------------------
1 | while (true) {
2 | console.log("Elsa");
3 | }
4 |
--------------------------------------------------------------------------------
/testing/ops/event.js:
--------------------------------------------------------------------------------
1 | var ee = new EventEmitter();
2 | ee.defineEvents(["01", "02", "03"]);
3 |
4 | ee.addListener("01", (x) => {
5 | console.log(x);
6 | });
7 |
8 | ee.emitEvent("01", ["some emitted data"]);
9 |
--------------------------------------------------------------------------------
/testing/ops/pending_jobs.js:
--------------------------------------------------------------------------------
1 | // TODO(@qu4k): remove when tla is supported
2 | (async () => {
3 | const promise = new Promise((resolve) => {
4 | resolve(anotherHello());
5 | });
6 | console.log(await await promise);
7 | })();
8 |
9 | async function anotherHello() {
10 | const promise = new Promise((resolve) => {
11 | resolve("Hello World");
12 | });
13 | return promise;
14 | }
15 |
--------------------------------------------------------------------------------
/testing/pkg_test.go:
--------------------------------------------------------------------------------
1 | package testing
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/elsaland/elsa/packager"
7 | )
8 |
9 | func TestPkg(t *testing.T) {
10 | packager.PkgSource("miscellaneous/args.js")
11 | }
12 |
--------------------------------------------------------------------------------
/testing/plugin/plugin.js:
--------------------------------------------------------------------------------
1 | Elsa.runPlugin("./test_plugin/plugin.so", "Elsa");
2 |
--------------------------------------------------------------------------------
/testing/plugin/test_plugin/plugin.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | var V interface{}
6 |
7 | func ElsaPlugin() interface{} {
8 | fmt.Printf("Hello, %s\n", V)
9 | return "Some returned data"
10 | }
11 |
--------------------------------------------------------------------------------
/testing/test_server.go:
--------------------------------------------------------------------------------
1 | package testing
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | )
7 |
8 | // StartTestServer start a new local test server for bundler tests.
9 | func StartTestServer() {
10 | http.Handle("/", http.FileServer(http.Dir(".")))
11 | log.Fatal(http.ListenAndServe(":8100", nil))
12 | }
13 |
--------------------------------------------------------------------------------
/testing/tests/mode_test.js:
--------------------------------------------------------------------------------
1 | import { eq } from "./utils.ts";
2 |
3 | Elsa.tests({
4 | "test mode == `test`": function () {
5 | eq(Elsa.mode, "test");
6 | },
7 | });
8 |
--------------------------------------------------------------------------------
/testing/tests/test1_test.js:
--------------------------------------------------------------------------------
1 | import { add, eq } from "./utils.ts";
2 |
3 | Elsa.tests({
4 | "adds numbers #2": function () {
5 | eq(6, add(2, 4));
6 | eq(6.6, add(2.6, 4));
7 | },
8 |
9 | "subtracts numbers #2": function () {
10 | eq(-2, add(2, -4));
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/testing/tests/test_test.js:
--------------------------------------------------------------------------------
1 | import { add, eq } from "./utils.ts";
2 |
3 | Elsa.tests({
4 | "adds numbers": function () {
5 | eq(6, add(2, 4));
6 | eq(6.6, add(2.6, 4));
7 | },
8 |
9 | "subtracts numbers": function () {
10 | eq(-2, add(2, -4));
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/testing/tests/utils.ts:
--------------------------------------------------------------------------------
1 | // Utility functions for testing the test runner ;)
2 | // (also tests imports...for some reason it didn't feel good without them)
3 |
4 | export function add(a: number, b: number) {
5 | return a + b;
6 | }
7 |
8 | export function eq(a: number | string | boolean, b: number | string | boolean) {
9 | if (!deepEqual(a, b)) {
10 | throw new Error(`Assertion failed: ${a} !== ${b}\n`);
11 | }
12 | }
13 |
14 | export function deepEqual(a: any, b: any) {
15 | if (Object.is(a, b)) {
16 | // items are identical
17 | return true;
18 | } else if (
19 | typeof a === "object" &&
20 | a !== null &&
21 | typeof b === "object" &&
22 | b !== null
23 | ) {
24 | // items are objects - do a deep property value compare
25 | // join keys from both objects together in one array
26 | let keys = Object.keys(a).concat(Object.keys(b));
27 | // filter out duplicate keys
28 | keys = keys.filter(function (value, index, self) {
29 | return self.indexOf(value) === index;
30 | });
31 | for (const p of keys) {
32 | if (typeof a[p] === "object" && typeof b[p] === "object") {
33 | if (!deepEqual(a[p], b[p])) {
34 | return false;
35 | }
36 | } else if (a[p] !== b[p]) {
37 | return false;
38 | }
39 | }
40 | return true;
41 | } else {
42 | return false;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/testing/typescript/import.ts:
--------------------------------------------------------------------------------
1 | import * as abc from "./typeerror.ts";
2 | import * as bc from "./wrong.ts";
3 | import { hello } from "https://x.nest.land/arweave-hello@0.0.2/mod.ts";
4 |
5 | hello("Elsa");
6 | console.log(abc, bc)
--------------------------------------------------------------------------------
/testing/typescript/typeerror.ts:
--------------------------------------------------------------------------------
1 | const str: string = 1;
2 |
3 | console.oops()
4 |
5 | const y = 1
6 | y += 1
7 |
8 | export const blabla = 1;
--------------------------------------------------------------------------------
/testing/typescript/wrong.ts:
--------------------------------------------------------------------------------
1 | var y: string = 1;
2 |
--------------------------------------------------------------------------------
/testing/web/console_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test console.log - string": function () {
3 | console.log("This is a string");
4 | },
5 | "test console.log - number": function () {
6 | console.log(12345);
7 | },
8 | "test console.log (number + string)": function () {
9 | console.log(1 + " is a string!");
10 | },
11 | "test console.log (nested object)": function () {
12 | console.log({
13 | super: {
14 | nested: {
15 | object: {
16 | it: {
17 | is: {
18 | indeed: true,
19 | },
20 | },
21 | },
22 | },
23 | },
24 | });
25 | },
26 | "test console.log - array with nested types": function () {
27 | console.log([
28 | 1,
29 | 2,
30 | 3,
31 | "woop, a string",
32 | {
33 | oops: {
34 | coords: [
35 | {
36 | x: 1,
37 | y: 2,
38 | },
39 | {
40 | x: 2,
41 | y: 1,
42 | },
43 | ],
44 | },
45 | },
46 | ["umm", "nested", ["arrays"]],
47 | ]);
48 | },
49 | "test console.log - functions": function () {
50 | console.log(console.log);
51 | },
52 | });
53 |
--------------------------------------------------------------------------------
/testing/web/encode_test.js:
--------------------------------------------------------------------------------
1 | const encoder = new TextEncoder();
2 | const decoder = new TextDecoder();
3 | import { eq } from "../tests/utils.ts";
4 |
5 | const text = "Elsa";
6 |
7 | Elsa.tests({
8 | "encode & decode": function () {
9 | const encoded = encoder.encode(text);
10 | const decoded = decoder.decode(encoded);
11 | eq(text, decoded);
12 | },
13 | "atob & btoa": function () {
14 | eq(btoa("Hello, world!"), "SGVsbG8sIHdvcmxkIQ==");
15 | eq(atob("SGVsbG8sIHdvcmxkIQ=="), "Hello, world!");
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/testing/web/fetch_test.js:
--------------------------------------------------------------------------------
1 | Elsa.tests({
2 | "test fetch API": async function () {
3 | try {
4 | fetch("https://google.com");
5 | } catch (e) {
6 | throw new Error(e);
7 | }
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/util/check.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/elsaland/quickjs"
9 | )
10 |
11 | func Check(err error) {
12 | if err != nil {
13 | var evalErr *quickjs.Error
14 | if errors.As(err, &evalErr) {
15 | fmt.Println(evalErr.Cause)
16 | fmt.Println(evalErr.Stack)
17 | }
18 | Panic(err)
19 | }
20 | }
21 |
22 | // Panic pretty print the error and exit with status code 1
23 | func Panic(err error) {
24 | LogError("Error", fmt.Sprintf("%v", err))
25 | os.Exit(1)
26 | }
27 |
--------------------------------------------------------------------------------
/util/log.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "fmt"
5 | "github.com/fatih/color"
6 | )
7 |
8 | func LogError(scope, format string, a ...interface{}) {
9 | fmt.Fprintf(color.Output, "%s: %s\n", color.New(color.FgRed, color.Bold).Sprint(scope), fmt.Sprintf(format, a...))
10 | }
11 |
12 | func LogInfo(scope, format string, a ...interface{}) {
13 | fmt.Fprintf(color.Output, "%s: %s\n", color.New(color.FgGreen, color.Bold).Sprint(scope), fmt.Sprintf(format, a...))
14 | }
15 |
--------------------------------------------------------------------------------