Giter Club home page Giter Club logo

babashka's Introduction

Babashka

CircleCI project chat Financial Contributors on Open Collective Clojars Project twitter docs

Life's too short to remember how to write Bash code. I feel liberated.

@laheadle on Clojurians Slack

Introduction

Babashka is a native Clojure interpreter for scripting with fast startup. Its main goal is to leverage Clojure in places where you would be using bash otherwise.

As one user described it:

I’m quite at home in Bash most of the time, but there’s a substantial grey area of things that are too complicated to be simple in bash, but too simple to be worth writing a clj/s script for. Babashka really seems to hit the sweet spot for those cases.

Goals

  • Fast starting Clojure scripting alternative for JVM Clojure
  • Easy installation: grab the self-contained binary and run. No JVM needed.
  • Familiar: targeted at JVM Clojure users
  • Cross-platform: supports linux, macOS and Windows
  • Interop with commonly used classes (System, File, java.time.*, java.nio.*)
  • Multi-threading support (pmap, future)
  • Batteries included (tools.cli, cheshire, ...)

Non-goals

  • Provide a mixed Clojure/Bash DSL (see portability).
  • Replace existing shells. Babashka is a tool you can use inside existing shells like bash and it is designed to play well with them. It does not aim to replace them.

Quickstart

For installation options check Installation. For quick installation use:

$ bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install)

or grab a binary from Github releases yourself and place it anywhere on the path.

Then you're ready to go:

$ ls | bb -i '(filter fs/directory? *input*)'
("doc" "resources" "sci" "script" "src" "target" "test")
bb took 4ms.

Support ❤️

You can support this project via Github Sponsors, OpenCollective, Ko-fi or indirectly via Clojurists Together.

Top sponsors

Babashka users

See companies for a list of companies using babashka.

Are you using babashka in your company or personal projects? Let us know here.

Setting expectations

Babashka uses SCI for interpreting Clojure. SCI implements a substantial subset of Clojure. Interpreting code is in general not as performant as executing compiled code. If your script takes more than a few seconds to run or has lots of loops, Clojure on the JVM may be a better fit as the performance on JVM is going to outweigh its startup time penalty. Read more about the differences with Clojure here.

Status

Functionality regarding clojure.core and java.lang can be considered stable and is unlikely to change. Changes may happen in other parts of babashka, although we will try our best to prevent them. Always check the release notes or CHANGELOG.md before upgrading.

Talk

To get an overview of babashka, you can watch this talk (slides):

Babashka at ClojureD 2020

Babashka book

The babashka book contains detailed information about how to get the most out of babashka scripting.

There is also the book Babashka Babooka, by Daniel Higginbotham, who has also helped a lot of people learn Clojure with Clojure for the Brave and True.

Examples

Read the output from a shell command as a lazy seq of strings:

$ ls | bb -i '(take 2 *input*)'
("CHANGES.md" "Dockerfile")

Read EDN from stdin and write the result to stdout:

$ bb '(vec (dedupe *input*))' <<< '[1 1 1 1 2]'
[1 2]

Read more about *input* and in- and output flags here.

Execute a script. E.g. print the current time in California using the java.time API:

File pst.clj:

#!/usr/bin/env bb

(def now (java.time.ZonedDateTime/now))
(def LA-timezone (java.time.ZoneId/of "America/Los_Angeles"))
(def LA-time (.withZoneSameInstant now LA-timezone))
(def pattern (java.time.format.DateTimeFormatter/ofPattern "HH:mm"))
(println (.format LA-time pattern))
$ bb pst.clj
05:17

More examples can be found here.

Try online

You can try babashka online with Nextjournal's babashka notebook environment.

Installation

Brew

Linux and macOS binaries are provided via brew.

Install:

brew install borkdude/brew/babashka

Upgrade:

brew upgrade babashka

Nix

Linux and macOS (including ARM Macs) binaries are provided via nix (see the installation instructions for nix here).

Install:

# Adding `nixpkgs-unstable` channel for more up-to-date binaries, skip this if you already have `nixpkgs-unstable` in your channel list
nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable
nix-channel --update
nix-env -iA nixpkgs-unstable.babashka

Upgrade:

nix-channel --update
nix-env -iA nixpkgs-unstable.babashka

You can find more documentation on how to use babashka with nix here.

Alpine

On Alpine it's recommended to download the binary manually from Github Releases and use the static linux binary.

Arch (Linux)

babashka is available in the Arch User Repository. It can be installed using your favorite AUR helper such as yay, yaourt, apacman and pacaur. Here is an example using yay:

yay -S babashka-bin

asdf

asdf is an extendable version manager for linux and macOS. Note that asdf will add significant startup time to any babashka script, consider using mise instead.

Babashka can be installed using a plugin as follows:

asdf plugin add babashka https://github.com/pitch-io/asdf-babashka
asdf install babashka latest

mise

mise is a development environment setup tool for linux and macOS.

Install:

mise use --global babashka@latest

Upgrade:

mise upgrade babashka

Windows

Scoop

On Windows you can install using scoop and the scoop-clojure bucket.

Or just follow these concrete steps:

# Note: if you get an error you might need to change the execution policy (i.e. enable Powershell) with
# Set-ExecutionPolicy RemoteSigned -scope CurrentUser
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')

scoop bucket add scoop-clojure https://github.com/littleli/scoop-clojure
scoop bucket add extras
scoop install babashka

Manual

If scoop does not work for you, then you can also just download the bb.exe binary from Github releases and place it on your path manually.

WSL1

Note: WSL1 users might experience a BSOD, please use the --static install option when installing

$ curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install
$ chmod +x install
$ ./install --static

Installer script

Install via the installer script for linux and macOS:

$ curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install
$ chmod +x install
$ ./install

By default this will install into /usr/local/bin (you may need sudo for this). To change this, provide the directory name:

$ ./install --dir .

To install a specific version, the script also supports --version:

$ ./install --dir . --version 0.4.1

To force the download of the zip archive to a different directory than /tmp use the --download-dir argument:

$ ./install --dir . --version 0.4.1 --download-dir .

On Linux, if you want to install the static binary version:

$ ./install --dir . --version 0.4.1 --download-dir . --static

In case you want to check the download, you can use the --checksum option. This maybe useful for unattended installations:

$ sha256sum babashka-0.4.1-linux-amd64-static.tar.gz
ab70fb39fdbb5206c0a2faab178ffb54dd9597991a4bc13c65df2564e8f174f6  babashka-0.4.1-linux-amd64-static.tar.g
$ ./install --dir /tmp --checksum ab70fb39fdbb5206c0a2faab178ffb54dd9597991a4bc13c65df2564e8f174f6 --static --version 0.4.1

Note that the --checksum option only works when --version option is also provided. This is to avoid breakage when a new version of Babashka is released.

Github releases

You may also download a binary from Github. For linux there is a static binary available which can be used on Alpine.

CI

  • On Github Actions it's recommended to use setup-clojure with bb: latest.
  • You can use the installer script on any non-Windows CI system. CircleCI requires sudo.
  • On Appveyor + Windows you can use a bit of Powershell.

Docker

Check out the image on Docker hub.

Check out the news page to keep track of babashka-related news items.

Go here to see the full list of built-in namespaces.

A list of projects (scripts, libraries, pods and tools) known to work with babashka.

Badges

bb compatible

The babashka compatible badge indicates that a library can be used as babashka dependency.

If this is the case for your library, we encourage you to proudly display this badge.

Markdown
[![bb compatible](https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg)](https://babashka.org)
AsciiDoc
https://babashka.org[image:https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg[bb compatible]]
HTML
<a href="https://babashka.org" rel="nofollow"><img src="https://github.com/babashka/babashka/raw/master/logo/badge.svg" alt="bb compatible" style="max-width: 100%;"></a>

bb built-in

The babashka built-in badge means that a library has been built directly into babashka and requires no extra dependencies to use it.

If this rare honor belongs to your library, you should display this badge.

Markdown
[![bb built-in](https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg)](https://babashka.org)
AsciiDoc
https://babashka.org[image:https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg[bb built-in]]
HTML
<a href="https://babashka.org" rel="nofollow"><img src="https://github.com/babashka/babashka/raw/master/logo/built-in-badge.svg" alt="bb built-in" style="max-width: 100%;"></a>

Swag

Pods are programs that can be used as a Clojure library by babashka. Documentation is available in the pod library repo.

A list of available pods can be found in the pod registry.

Differences with Clojure

Babashka is implemented using the Small Clojure Interpreter. This means that a snippet or script is not compiled to JVM bytecode, but executed form by form by a runtime which implements a substantial subset of Clojure. Babashka is compiled to a native binary using GraalVM. It comes with a selection of built-in namespaces and functions from Clojure and other useful libraries. The data types (numbers, strings, persistent collections) are the same. Multi-threading is supported (pmap, future).

Differences with Clojure:

  • A pre-selected set of Java classes are supported. You cannot add Java classes at runtime.

  • Interpretation comes with overhead. Therefore loops are slower than in Clojure on the JVM. In general interpretation yields slower programs than compiled programs.

  • No deftype, definterface and unboxed math.

  • defprotocol and defrecord are implemented using multimethods and regular maps. Ostensibly they work the same, but under the hood there are no Java classes that correspond to them.

  • Currently reify works only for one class at a time

  • The clojure.core.async/go macro is not (yet) supported. For compatibility it currently maps to clojure.core.async/thread. More info here.

Package babashka script as a AWS Lambda

AWS Lambda runtime doesn't support signals, therefore babashka has to disable handling of SIGINT and SIGPIPE. This can be done by setting BABASHKA_DISABLE_SIGNAL_HANDLERS to true.

Articles, podcasts and videos

Including new libraries or classes

Before new libraries or classes go into the standardly distributed babashka binary, these evaluation criteria are considered:

  • The library or class is useful for general purpose scripting.
  • Adding the library or class would make babashka more compatible with Clojure libraries relevant to scripting.
  • The library cannot be interpreted by with babashka using --classpath.
  • The functionality can't be met by shelling out to another CLI or can't be written as a small layer over an existing CLI (like babashka.curl) instead.
  • The library cannot be implemented as a pod.

If not all of the criteria are met, but adding a feature is still useful to a particular company or niche, adding it behind a feature flag is still a possibility. This is currently the case for next.jdbc and the PostgresQL and HSQLDB database drivers. Companies interested in these features can compile an instance of babashka for their internal use. Companies are also free to make forks of babashka and include their own internal libraries. If their customized babashka is interesting to share with the world, they are free to distribute it using a different binary name (like bb-sql, bb-docker, bb-yourcompany, etc.). See the feature flag documentation and the implementation of the existing feature flags (example commit).

Related projects

Contributors

Thanks to all the people that contributed to babashka:

License

Copyright © 2019-2020 Michiel Borkent

Distributed under the EPL License. See LICENSE.

This project contains code from:

  • Clojure, which is licensed under the same EPL License.

babashka's People

Contributors

agata-anastazja avatar agilecreativity avatar arichiardi avatar bobisageek avatar borkdude avatar cap10morgan avatar cldwalker avatar djblue avatar eerohele avatar grazfather avatar grzm avatar hansbugge avatar holyjak avatar ieugen avatar jeroenvandijk avatar kah0ona avatar kipz avatar kolharsam avatar lispyclouds avatar lread avatar mpenet avatar nikvdp avatar pez avatar plexus avatar rwstauner avatar sogaiu avatar teodorlu avatar thiagokokada avatar victorb avatar wilkerlucio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

babashka's Issues

Using field accessors within a (try ...) block gives errors

version

bb -h
babashka v0.0.30

platform

OSX

problem

While working with exceptions I came across the issue below. There is something with the try macro and accessing java members

repro

bb -e '(try (throw (ex-info "foo" {})) (catch Exception e (.getMessage e)))'
No matching method g found taking 9 args for class clojure.lang.ExceptionInfo [at line 1, column 52]

Or even

bb -e '(try (.getMessage (ex-info "foo" {})))'

Or just something with try

bb -e '(try (.toString "foo"))'
No matching method t found taking 7 args for class java.lang.String [at line 1, column 1]

expected behavior

bb -e '(.getMessage (ex-info "foo" {}))'
"foo"

Be able to spawn, kill processes and redirect in, out and err

Maybe just use conch. This already works in the conch branch:

Pipe processes

(let [grep (conch/proc "grep" "README")
      in (str/join "\n" *in*)
      _ (conch/feed-from-string grep in)
      _ (conch/done grep)
      out (conch/stream-to-string grep :out)]
  out)
$ ls | bb -io -f /tmp/script.clj
README.md

TODO: Since #(str/join "\n" %) is so common, we should probably add a function for it.

Spawning two processes that interact and kill them afterwards:

(def server (conch/proc "./bb" "--socket-repl" "1666"))
(def client (conch/proc "bash" "-c" "rlwrap nc 127.0.0.1 1666"))
(conch/feed-from-string client "(+ 1 2 3)\n")
(conch/done client)
(let [ret (conch/stream-to-string client :out)]
  (conch/destroy server)
  (conch/destroy client)
  ret)
Babashka v0.0.16-SNAPSHOT REPL.
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.

bb=> 6
bb=>
bb took 22ms.

Opening a HTTP server, do a request and kill the server

(defn wait-for-port [p]
  (if (zero? (conch/exit-code (conch/proc "nc" "-z" "127.0.0.1" (str p))))
    0
    (do (shell/sh "sleep" ".1")
        (wait-for-port p))))

(def http (conch/proc "python" "-m" "SimpleHTTPServer" "1337"))
(wait-for-port 1337)
(def curl (conch/proc "curl" "127.0.0.1:1337/README.md"))

(def README (conch/stream-to-string :out curl))

(conch/destroy http)

(take 3 (str/split-lines README))

Termux build

This project is extremely cool, exactly what I've wanted.

Any chance to build for Termux (Android)?

[idea] map over clj-kondo analysis EDN stream and save results to database

I can imagine clj-kondo support streaming the analysis as well, so another process could tail it and build up a database that gets updated while you edit your code.
that "another process" could be editor tooling
maybe bb could have some database functionality, e.g. around sqlite, not sure about that yet, but it would be pretty cool to do database actions over a stream of EDN
maybe there's already another sqlite command line tool which can just be called from bb

$ sqlite3 test.db 'create table foo (email text not null);'

$ echo '{:email "[email protected]"}' | bb '(let [email (:email *in*)] (shell/sh "sqlite3" "test.db" (format "insert into foo values (\"%s\")" email)))'
{:exit 0, :out "", :err ""}

$ sqlite3 test.db "select * from foo"
[email protected]

Can't use `bb -i '*in*' in eshell

version
0.0.13

platform
MacOS + Emacs 26.1

problem
This works:

$ bb -i '(+ 1 2 3)'
6

This hangs until I interrupt:

$ ls | bb -i '*in*'
interrupt: 2

It works in my MacOS terminal, but I basically only use emacs, so it'd be neat if it worked in eshell, and I think it did before I updated bb.

repro
Call $ ls | bb -i '*in*' in eshell.

expected behavior
Not hanging.

Can't destructure vector

version
0.0.7

platform
MacOS

problem
When trying to destructure a vector, I get an error I don't understand.

repro

$ bb -i '(let [[keys & rest] [1 2 3]] keys)'
Could not resolve symbol: seq.

expected behavior
I'd get 1 back.

implement load-file

Using load-file people can make something like a Prelude to their scripts by including some commonly used functions.

str/join doesn't work?

This example from the README:

cat /tmp/test.txt | bb --raw '(shuffle *in*)' | bb --println '(str/join "\n" *in*)

Prints nil when I try it. MacOS Mojave. bb 0.0.3.

Different behavior via socket repl

version

945b768

platform

Manjaro Linux

problem

Output differed between execution at the command line compared to socket repl.

repro

command line:

./bb "(defmacro bindings [] (mapv #(list 'quote %) (keys &env))) (defn bar [x] (println (bindings)) x) (bar 1)"

yielded the output:

[x]
1

via the socket repl:

bb=> (defmacro bindings [] (mapv #(list 'quote %) (keys &env))) (defn bar [x] (println (bindings)) x) (bar 1)

yielded the output:

[%1]
1

expected behavior

Expected to see [x], not [%1]

Socket REPL hangs in an infinite loop if you pass `)`

version
v0.0.22

platform
Ubuntu 18.02

problem
If you connect to a Socket REPL and try to evaluate ) (only the close parens), the REPL hangs with:

bb=> Unmatched delimiter: ) [at line 1, column 1]
bb=> Unmatched delimiter: ) [at line 1, column 1]
bb=> Unmatched delimiter: ) [at line 1, column 1]
bb=> Unmatched delimiter: ) [at line 1, column 1]
bb=> Unmatched delimiter: ) [at line 1, column 1]
bb=> Unmatched delimiter: ) [at line 1, column 1]

And keeps printing until you disconnect.

Reader conditional problems with :default

Hello, thanks for the project and for implementing reader conditionals!

I found a strange problem when trying to detect the current REPL:

version
v0.0.22

platform
Ubuntu 18.02

problem
When using reader conditionals with both :bb and :default, Babashka is ignoring :bb

repro

╰─>$ bb -e '#?(:bb :using-bb :default :unknown)'
:unknown

expected behavior
Return :using-bb instead

Add clojure.pprint

Is your feature request related to a problem? Please describe.
When printing data it could be nice to have access to clojure.pprint and more specifically clojure.pprint/pprint. :)

Describe the solution you'd like
Adding a alias, e.g. clojure.pprint as pp.

Describe alternatives you've considered
Since this is most useful when "debugging"/playing around, the other thing I was thinking about is if one could run e.g. ls | bb -r to start a repl process where *in* is set to what ls pipes to bb. But I figure that might be overkill. This request makes the assumption that adding clojure.pprint is simple, and won't interfere with other plans. If that assumption is false, this request can safely be ignored.

wait-for-it function

Implement it in Clojure so it's not dependent on netcat:

https://stackoverflow.com/questions/18616740/how-to-make-client-socket-wait-for-server-socket

Right now you can do this using:

(defn wait-for-port [p]
  (if (zero? (conch/exit-code (conch/proc "nc" "-z" "127.0.0.1" (str p))))
    0
    (do (shell/sh "sleep" ".1")
        (wait-for-port p))))

Maybe in a namespace:

bb.misc or bb.utils or just utils

When there will be more such useful functions, they can be be aliased in a new namespace as well.

Unable to compile

version

[ Please specify which version of babashka you're using. You can find this with babashka --version. The documentation on the master branch may be ahead of the most released version. You can check the docs for your version by going to cljdoc. ]

platform

[ Please specify which platform you are using babashka on, so you can test a
new binary when the issue is resolved. ]

problem

[ Please provide a short and to the point description of the problem ]

repro

[ Please provide a minimal working reproduction of the problem ]

expected behavior

[ What is the behavior you expected to see? Please provide a minimal working example ]

Expose `clojure.string/escape`

Sometimes it is useful to be able to replace characters in text with other strings, for example to escape chars with special meaning as in here:

(defn re-quote [s]
   (clojure.string/escape s {\? "\\?", \. "\\?"}))

(replace-with-sed my-file 
  (re-quote ":draft? false") 
  (re-quote "draft? true"))

v0.0.7: allow shell commands

TODO:

--- from before: ----

  • don't read stdin when *in* is not present in the input program
  • how to deal with shell commands that return exit code 1?
  • define do in sci and check when behavior now that side effects are possible
  • revert automatically promoted commands, leave that decision for the future
  • what does it mean when bb receives multiple program arguments?

document goals + design choices + differences from other tools

  • Native scripting tool for Clojure, low latency, fast feedback.
  • Low migration barrier from bash -> bash + inline clojure or babashka script -> JVM Clojure.
    Embrace bash, it's the place where all shell scripts are born. Bb is easily integrable in existing bash scripts as a single Clojure expression, as part of a pipe chain, or script. No hybrid bash+clojure syntax/DSL so normal Clojure tooling works without adaptations.
  • Multi-threading support
  • Batteries included (important scripting tools/libs are included like clojure.tools.cli).

Error resolving quoted symbols inside try

On Babashka 0.0.30, on Ubuntu 18.04 (last release) it can't detect quoted symbols inside a try block, so the following code fails:

(try 'a-symbol)

Expected to return a-symbol, returns:

Could not resolve symbol: a-symbol [at line 1, column 6]

respect PIPE signal

Example:

./bb -o '(range)' |
./bb --stream '(* *in* *in*)' |
head -n10

This currently doesn't terminate, because we don't respect the PIPE signal.

--stream (streaming mode)

As discussed on slack with @justone, it's useful to have a streaming mode where the tools (both jet and bb) process one EDN value after another, until the stream is closed. To be explicit about this, we can introduce a --stream flag.

Also see jqlang/jq#684 (comment).

Expose `java.util.regex.Pattern/quote` so that we can escape RegExp-es

Sometimes it is useful to ensure a text is passed literally to a tool that support regular expressions, in which case it needs to be quoted to escape all special RegExp characters. Java has java.util.regex.Pattern/quote for that:

(defn re-quote [s]
   (java.util.regex.Pattern/quote s))

(replace-with-sed my-file 
  (re-quote ":rating :?") 
  (re-quote ":rating :M+"))

Make it possible to write to a file via binding / .write / .append

Currently I do not see a way to write into a file in bb. In Clojure I'd do one of these

(with-open [wrt (io/writer "my/file.txt")]
   (binding [*out* wrt] (println "hi"))
  (.write wrt "hi\n")
  (.append wrt "hi\n")

I am trying to change a number of files so writing to bb's stdout and redirecting to file won't help.

however none works in babashka. Or is there another way? Thank you!

document /usr/bin/env workaround for linux

I think this should be the preferred way for scripts, since it works on both linux and MacOS and might be more flexible than using /usr/bin/env.

#!/bin/sh

#_(
   exec bb -f "$0" "$@"
   )

(+ 1 2 3)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.