Skip to content

Commit

Permalink
Merge be6ed0b into c5b19d3
Browse files Browse the repository at this point in the history
  • Loading branch information
odino committed Sep 18, 2019
2 parents c5b19d3 + be6ed0b commit c6e81d0
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
docs/_site
*.ignore
test-ignore-*
vendor
builds/*
!builds/.gitkeep
Expand Down
1 change: 1 addition & 0 deletions docs/_includes/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

## Miscellaneous

* [Installing 3rd party libraries](/misc/3pl)
* [Errors](/misc/error)
* [Configuring the REPL](/misc/configuring-the-repl)
* [Runtime](/misc/runtime)
Expand Down
92 changes: 92 additions & 0 deletions docs/misc/3pl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Installing 3rd party libraries

The ABS interpreter comes with a built-in installer for 3rd party libraries,
very similar to `npm install`, `pip install` or `go get`.

The installer, budled since the `1.8.0` release, is currently **experimental**
and a few things might change.

In order to install a package, you simply need to run `abs get`:

``` bash
$ abs get github.com/abs-lang/abs-sample-module
🌘 - Downloading archive
Unpacking...
Creating alias...
Install Success. You can use the module with `require("abs-sample-module")`
```

Modules will be saved under the `vendor/$MODULE-master` directory. Each module
also gets an alias to facilitate requiring them in your code, meaning that
both of these forms are supported:

```
⧐ require("abs-sample-module/sample.abs")
{"another": f() {return hello world;}}
⧐ require("vendor/github.com/abs-lang/abs-sample-module-master/sample.abs")
{"another": f() {return hello world;}}
```

Note that the `-master` prefix [will be removed](https://github.com/abs-lang/abs/issues/286) in future versions of ABS.

Module aliases are saved in the `packages.abs.json` file
which is created in the same directory where you run the
`abs get ...` command:

```
$ abs get github.com/abs-lang/abs-sample-module
🌗 - Downloading archive
Unpacking...
Creating alias...
Install Success. You can use the module with `require("abs-sample-module")`
$ cat packages.abs.json
{
"abs-sample-module": "./vendor/github.com/abs-lang/abs-sample-module-master"
}
```

If an alias is already taken, the installer will let you know that you
will need to use the full path when requiring the module:

```
$ echo '{"abs-sample-module": "xyz"}' > packages.abs.json
$ abs get github.com/abs-lang/abs-sample-module
🌘 - Downloading archive
Unpacking...
Creating alias...This module could not be aliased because module of same name exists
Install Success. You can use the module with `require("./vendor/github.com/abs-lang/abs-sample-module-master")`
```

When requiring a module, ABS will try to load the `index.abs` file unless
another file is specified:

```
$ ~/projects/abs/builds/abs
Hello alex, welcome to the ABS (1.8.0) programming language!
Type 'quit' when you're done, 'help' if you get lost!
⧐ require("abs-sample-module")
{"another": f() {return hello world;}}
⧐ require("abs-sample-module/index.abs")
{"another": f() {return hello world;}}
⧐ require("abs-sample-module/another.abs")
f() {return hello world;}
```

## Supported hosting platforms

Currently, the installer supports modules hosted on:

* GitHub

## Next

That's about it for this section!

You can now head over to read a little bit about [errors](/misc/error).
2 changes: 1 addition & 1 deletion docs/types/builtin-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,4 +339,4 @@ statements until changed.
That's about it for this section!
You can now head over to read a little bit about [errors](/misc/error).
You can now head over to read a little bit about [how to install 3rd party libraries](/misc/3pl).
8 changes: 4 additions & 4 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -945,10 +945,10 @@ c")`, []string{"a", "b", "c"}},
{`sleep(0.01)`, nil},
{`$()`, ""},
{`a = 1; eval("a")`, 1},
{`"a = 2; return 10" >> "test-source-vs-require.abs.ignore"; a = 1; x = source("test-source-vs-require.abs.ignore"); a`, 2},
{`"a = 2; return 10" >> "test-source-vs-require.abs.ignore"; a = 1; x = require("test-source-vs-require.abs.ignore"); a`, 1},
{`"a = 2; return 10" >> "test-source-vs-require.abs.ignore"; a = 1; x = source("test-source-vs-require.abs.ignore"); x`, 10},
{`"a = 2; return 10" >> "test-source-vs-require.abs.ignore"; a = 1; x = require("test-source-vs-require.abs.ignore"); x`, 10},
{`"a = 2; return 10" >> "test-ignore-source-vs-require.abs"; a = 1; x = source("test-ignore-source-vs-require.abs"); a`, 2},
{`"a = 2; return 10" >> "test-ignore-source-vs-require.abs"; a = 1; x = require("test-ignore-source-vs-require.abs"); a`, 1},
{`"a = 2; return 10" >> "test-ignore-source-vs-require.abs"; a = 1; x = source("test-ignore-source-vs-require.abs"); x`, 10},
{`"a = 2; return 10" >> "test-ignore-source-vs-require.abs"; a = 1; x = require("test-ignore-source-vs-require.abs"); x`, 10},
{`[[1,2,3], [2,3,4]].tsv()`, "1\t2\t3\n2\t3\t4"},
{`[1].tsv()`, "tsv() must be called on an array of arrays or objects, such as [[1, 2, 3], [4, 5, 6]], '[1]' given"},
{`[{"c": 3, "b": "hello"}, {"b": 20, "c": 0}].tsv()`, "b\tc\nhello\t3\n20\t0"},
Expand Down
33 changes: 17 additions & 16 deletions evaluator/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"crypto/rand"
"encoding/csv"
"encoding/json"
"fmt"
"io/ioutil"
"math"
Expand Down Expand Up @@ -1574,31 +1575,31 @@ func sourceFn(tok token.Token, env *object.Environment, args ...object.Object) o
// require("file.abs")
var history = make(map[string]string)

type StringFn func(string) (string, error)
var packageAliases map[string]string
var packageAliasesLoaded bool

func requireFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object {
getAlias := Memoize(util.ReadAliasFromFile)
a, error := getAlias(args[0].Inspect())
if error != nil {
return newError(tok, "error resolving '%s': %s\n", args[0].Inspect(), error.Error())
if !packageAliasesLoaded {
a, err := ioutil.ReadFile("./packages.abs.json")

// We couldn't open the packages, file, possibly doesn't exists
// and the code shouldn't fail
if err == nil {
// Try to decode the packages file:
// if an error occurs we will simply
// ignore it
json.Unmarshal(a, &packageAliases)
}

packageAliasesLoaded = true
}

a := util.UnaliasPath(args[0].Inspect(), packageAliases)
file := filepath.Join(env.Dir, a)
e := object.NewEnvironment(env.Writer, filepath.Dir(file))
return doSource(tok, e, file, args...)
}

func Memoize(fn StringFn) StringFn {
return func(str string) (string, error) {
if res, ok := history[str]; ok {
return res, nil
}
res, err := fn(str)
history[str] = res
return res, err
}
}

func doSource(tok token.Token, env *object.Environment, fileName string, args ...object.Object) object.Object {
err := validateArgs(tok, "source", args, 1, [][]string{{object.STRING_OBJ}})
if err != nil {
Expand Down
15 changes: 7 additions & 8 deletions install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ func valid(module string) bool {
}

func Install(module string) {

if !valid(module) {
fmt.Printf(`Error reading URL. Please use "github.com/USER/REPO" format to install`)
return
Expand All @@ -44,7 +43,7 @@ func Install(module string) {
return
}

func PrintLoader(done chan int64, message string) {
func printLoader(done chan int64, message string) {
var stop bool = false
symbols := []string{"🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "}
i := 0
Expand Down Expand Up @@ -92,7 +91,7 @@ func getZip(module string) error {
url := fmt.Sprintf("https://%s/archive/master.zip", module)

done := make(chan int64)
go PrintLoader(done, "Downloading archive")
go printLoader(done, "Downloading archive")

req, err := http.NewRequest("GET", url, nil)
if err != nil {
Expand Down Expand Up @@ -189,34 +188,34 @@ func createAlias(module string) (string, error) {

data := make(map[string]string)
moduleName := filepath.Base(module)
// Appending "master" as Github zip file has "-master" suffix
modulePath := fmt.Sprintf("./vendor/%s-master", module)

// If package.abs.json file is empty
if len(b) == 0 {
// Appending "master" as Github zip file has "-master" suffix
// Add alias key-value pair to file
data[moduleName] = modulePath

} else {
err = json.Unmarshal(b, &data)
if err != nil {
fmt.Printf("Could not unmarshal alias json %s\n", err)
return "", err
}

// module already installed and aliased
if data[moduleName] == modulePath {
return moduleName, nil
}

if data[moduleName] != "" {
fmt.Printf("This module could not be aliased because module of same name exists\n")
moduleName = module
data[moduleName] = modulePath
return modulePath, nil
}

data[moduleName] = modulePath
}

newData, err := json.MarshalIndent(data, "", " ")

if err != nil {
fmt.Printf("Could not marshal alias json when installing module %s\n", err)
return "", err
Expand Down
43 changes: 32 additions & 11 deletions util/util.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package util

import (
"encoding/json"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"regexp"
"strconv"
"strings"

"github.com/abs-lang/abs/object"
)
Expand Down Expand Up @@ -103,16 +102,38 @@ func UniqueStrings(slice []string) []string {
return list
}

func ReadAliasFromFile(path string) (string, error) {
var packageAlias map[string]string
a, _ := ioutil.ReadFile("./packages.abs.json")
err := json.Unmarshal(a, &packageAlias)
if err != nil {
return path, err
// UnaliasPath translates a path alias
// to the full path in the filesystem.
func UnaliasPath(path string, packageAlias map[string]string) string {
// An alias can come in different forms:
// - package
// - package/file.abs
// but we only really need to resolve the
// first path in the alias.
parts := strings.Split(path, string(os.PathSeparator))

if len(parts) < 1 {
return path
}

if packageAlias[parts[0]] != "" {
// If we are able to resolve a path, then
// we should join in back with the rest of the
// paths
p := []string{packageAlias[parts[0]]}
p = append(p, parts[1:]...)
path = filepath.Join(p...)
}
return appendIndexFile(path)
}

if packageAlias[path] != "" {
return packageAlias[path], nil
// If our path didn't end with an ABS file (.abs),
// let's assume it's a directory and we will
// auto-include the index.abs file from it
func appendIndexFile(path string) string {
if filepath.Ext(path) != ".abs" {
return filepath.Join(path, "index.abs")
}
return path, nil

return path
}
28 changes: 28 additions & 0 deletions util/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package util

import (
"os"
"testing"
)

func TestUnaliasPath(t *testing.T) {
tests := []struct {
path string
aliases map[string]string
expected string
}{
{"test", map[string]string{}, "test" + string(os.PathSeparator) + "index.abs"},
{"test" + string(os.PathSeparator) + "sample.abs", map[string]string{}, "test" + string(os.PathSeparator) + "sample.abs"},
{"test" + string(os.PathSeparator) + "sample.abs", map[string]string{"test": "path"}, "path" + string(os.PathSeparator) + "sample.abs"},
{"test", map[string]string{"test": "path"}, "path" + string(os.PathSeparator) + "index.abs"},
{"." + string(os.PathSeparator) + "test", map[string]string{"test": "path"}, "test" + string(os.PathSeparator) + "index.abs"},
}

for _, tt := range tests {
res := UnaliasPath(tt.path, tt.aliases)

if res != tt.expected {
t.Fatalf("error unaliasing path, expected %s, got %s", tt.expected, res)
}
}
}

0 comments on commit c6e81d0

Please sign in to comment.