raynes / conch Goto Github PK
View Code? Open in Web Editor NEWA flexible library for shelling out in Clojure
A flexible library for shelling out in Clojure
I tried the low level usage with cat, but it doesn't work like in the example. What could be the problem?
`playsync.core> (def p (sh/proc "cat"))
#'playsync.core/p
playsync.core> (future (sh/stream-to-out p :out))
#object[clojure.core$future_call$reify__8097 0x141df919 {:status :pending, :val nil}]
playsync.core> (sh/feed-from-string p "foo\n")
nil`
on some exe's under windows timeout works on others it does not
I'm seeking to use clojure for scripting lang instead of a bash. So, created a launcher.clj
script with lein-oneoff where I have to use commands like java
to start a jar.
(defdeps
[[org.clojure/clojure "1.6.0"]
[me.raynes/conch "0.8.0"]
[com.palletops/stevedore "0.8.0-beta.7"]])
(ns launch
(:require [me.raynes.conch :refer [programs with-programs let-programs] :as sh]
[me.raynes.conch.low-level :as shell]))
(println "################ Starting laucher #######################")
(programs echo)
(echo "hi")
(shell/stream-to-string (shell/proc "ls" "-l") :out) ;; prints nothing
;;(print (shell/stream-to-string (shell/proc "ls" "-l" "/usr/local/") :out)) ;; prints fine
(print (shell/stream-to-string (shell/proc "java" "--version") :out)) ;; no output
(println "################ Finished laucher #######################")
On lein oneoff launch.clj
, it doesn't shout the java version
which is set to $PATH
variable,
$ lein oneoff launch.clj
################ Starting laucher #######################
################ Finished laucher #######################
But version of bash
or /bin/bash
is printed.
(defdeps
[[org.clojure/clojure "1.6.0"]
[me.raynes/conch "0.8.0"]
[com.palletops/stevedore "0.8.0-beta.7"]])
(ns launch
(:require [me.raynes.conch :refer [programs with-programs let-programs] :as sh]
[me.raynes.conch.low-level :as shell]))
(println "################ Starting laucher #######################")
(programs echo)
(echo "hi")
(shell/stream-to-string (shell/proc "ls" "-l") :out) ;; prints nothing
;;(print (shell/stream-to-string (shell/proc "ls" "-l" "/usr/local/") :out)) ;; prints fine
(print (shell/stream-to-string (shell/proc "bash" "--version") :out)) ;; prints output
(println "################ Finished laucher #######################")
Whats the way get external commands get accessible in conch
?
I found the conch macros to have high cognitive overhead. For fear of side-effects, I went and glossed over let-programs
first before trying it.
It would be nice if conch could return a lazy program via let
that did path resolution at run-time, perhaps with existence (and permission) checking at definition time. E.g.:
(require '[me.raynes.conch :as sh])
(let [docker (sh/program "docker")]
(docker "build" "more args go here"))
I believe this would satisfy 99% of use-cases and would make Conch more approachable :).
I need conch
to execute a default.nix
which looks like this:
(http://nixos.org/nix/manual/#sec-nix-shell)
with import <nixpkgs> {};
{
someEnv = myEnvFun {
name = "someenv";
buildInputs = [ stdenv rustcMaster cargo nanomsg];
};
}
then I need to execute commands inside this nix shell that's just created.
How does one do this with conch
?
I am trying to use this on Windows 7 platform, with an ocr program called tesseract, but is not working.
I get the following exception. It doesn't help if i put verbose option or redirect output to a file. tesseract works fine as standard java shell command.
(programs tesseract)
(tesseract "C:/file.gif")
ExceptionInfo Program returned non-zero exit code 1 clojure.core/ex-info (core.clj:4403)
(with-program [cp]
(cp "-rf" "./target/*" "./"))
returns
{:proc
{:out (),
:in
#object[java.lang.UNIXProcess$ProcessPipeOutputStream 0x5b7600ec "java.lang.UNIXProcess$ProcessPipeOutputStream@5b7600ec"],
:err ("cp: ./target/*: No such file or directory\n"),
:process
#object[java.lang.UNIXProcess 0x43f23338 "java.lang.UNIXProcess@43f23338"]},
:exit-code #<Future@17d3bf09: 1>,
:stdout "",
:stderr "cp: ./target/*: No such file or directory\n"}
But the shell command works perfectly fine: cp -rf ./target/* ./
And now, with specific file and not wildcard:
(with-program [cp]
(cp "-rf" "./target/index.html" "./"))
Works correctly.
I'd like to have something like Python's check_call
(or, failing that, call
).
Conch does not cut it, because if I do {:out *out* :err *err*}
the stderr and stdout are interleaved incorrectly, making the output hard to follow.
At first I tried with {:err-redirect true}
, but I realized that storing the err would be more useful for error reporting and that with a long-running process (like make
) the output was weirdly not buffered every line, but every several lines (If you want to check this, I stumbled upon it with this code)...
I thus tried with {:out *out* :verbose true :seq true :buffer :none}
(with or without :seq
) but I got this strange error:
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: No matching method found: write for class java.io.OutputStreamWriter
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:188)
at clojure.core$deref_future.invoke(core.clj:2108)
at clojure.core$future_call$reify__6267.deref(core.clj:6308)
at clojure.core$deref.invoke(core.clj:2128)
at me.raynes.conch$run_command.invoke(conch.clj:159)
at me.raynes.conch$execute.doInvoke(conch.clj:186)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:619)
at leiningen.protobuf$execute$command__1980.doInvoke(protobuf.clj:41)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:617)
at leiningen.protobuf$execute.invoke(protobuf.clj:44)
at leiningen.protobuf$build_protoc.invoke(protobuf.clj:113)
at leiningen.protobuf$protobuf.doInvoke(protobuf.clj:162)
at clojure.lang.RestFn.invoke(RestFn.java:410)
at clojure.lang.Var.invoke(Var.java:415)
at clojure.lang.AFn.applyToHelper(AFn.java:161)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.core$apply.invoke(core.clj:619)
at leiningen.core.main$resolve_task$fn__3029.doInvoke(main.clj:189)
at clojure.lang.RestFn.invoke(RestFn.java:410)
at clojure.lang.AFn.applyToHelper(AFn.java:161)
at clojure.lang.RestFn.applyTo(RestFn.java:132)
at clojure.lang.AFunction$1.doInvoke(AFunction.java:29)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:619)
at leiningen.core.main$apply_task.invoke(main.clj:230)
at leiningen.core.main$resolve_and_apply.invoke(main.clj:234)
at leiningen.core.main$_main$fn__3092.invoke(main.clj:303)
at leiningen.core.main$_main.doInvoke(main.clj:290)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.lang.Var.invoke(Var.java:415)
at clojure.lang.AFn.applyToHelper(AFn.java:161)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.core$apply.invoke(core.clj:617)
at clojure.main$main_opt.invoke(main.clj:335)
at clojure.main$main.doInvoke(main.clj:440)
at clojure.lang.RestFn.invoke(RestFn.java:436)
at clojure.lang.Var.invoke(Var.java:423)
at clojure.lang.AFn.applyToHelper(AFn.java:167)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.main.main(main.java:37)
Caused by: java.lang.IllegalArgumentException: No matching method found: write for class java.io.OutputStreamWriter
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:80)
at clojure.lang.Reflector.invokeInstanceMethod(Reflector.java:28)
at me.raynes.conch$eval1794$fn__1795.invoke(conch.clj:28)
at me.raynes.conch$eval1751$fn__1752$G__1742__1763.invoke(conch.clj:8)
at me.raynes.conch$run_command$fn__1914.invoke(conch.clj:159)
at clojure.core$binding_conveyor_fn$fn__4107.invoke(core.clj:1836)
at clojure.lang.AFn.call(AFn.java:18)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
Unfortunately, I don't see "foo" in the output of feed-from-string in the following REPL session:
user> (use '[me.raynes.conch.low-level :as sh])
nil
user> (def p (sh/proc "cat"))
#'user/p
user> (future (sh/stream-to-out p :out))
#future[{:status :pending, :val nil} 0x19e48022]
user> (sh/feed-from-string p "foo\n")
nil
user>
Any idea, why this could be? I'm running a linux system, so cat generally is available.
I figured out now, that this behavior is only when run inside a emacs cider nrepl session. Outside of it, it works fine.
Occasionally (as in, it doesn't happen every time) when I run lein run
on a project that includes [me.raynes/conch "0.8.0"]
, I'll get
Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: ex-info in this context, compiling:(me/raynes/conch.clj:192)
at clojure.lang.Compiler.analyze(Compiler.java:6235)
at clojure.lang.Compiler.analyze(Compiler.java:6177)
at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3452)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6411)
at clojure.lang.Compiler.analyze(Compiler.java:6216)
at clojure.lang.Compiler.analyze(Compiler.java:6177)
at clojure.lang.Compiler$ThrowExpr$Parser.parse(Compiler.java:2239)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6409)
at clojure.lang.Compiler.analyze(Compiler.java:6216)
at clojure.lang.Compiler.analyze(Compiler.java:6177)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5572)
at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5008)
at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3629)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6407)
at clojure.lang.Compiler.analyze(Compiler.java:6216)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6397)
at clojure.lang.Compiler.analyze(Compiler.java:6216)
at clojure.lang.Compiler.access$100(Compiler.java:37)
at clojure.lang.Compiler$DefExpr$Parser.parse(Compiler.java:492)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6409)
at clojure.lang.Compiler.analyze(Compiler.java:6216)
at clojure.lang.Compiler.analyze(Compiler.java:6177)
at clojure.lang.Compiler.eval(Compiler.java:6469)
at clojure.lang.Compiler.load(Compiler.java:6902)
at clojure.lang.RT.loadResourceScript(RT.java:357)
at clojure.lang.RT.loadResourceScript(RT.java:348)
at clojure.lang.RT.load(RT.java:427)
at clojure.lang.RT.load(RT.java:398)
at clojure.core$load$fn__4610.invoke(core.clj:5386)
at clojure.core$load.doInvoke(core.clj:5385)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5200)
at clojure.core$load_lib.doInvoke(core.clj:5237)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invoke(core.clj:602)
at clojure.core$load_libs.doInvoke(core.clj:5271)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:602)
at clojure.core$require.doInvoke(core.clj:5352)
at clojure.lang.RestFn.invoke(RestFn.java:512)
at clojure_gui.core$eval20$loading__4505__auto____21.invoke(core.clj:1)
at clojure_gui.core$eval20.invoke(core.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6465)
at clojure.lang.Compiler.eval(Compiler.java:6455)
at clojure.lang.Compiler.load(Compiler.java:6902)
at clojure.lang.RT.loadResourceScript(RT.java:357)
at clojure.lang.RT.loadResourceScript(RT.java:348)
at clojure.lang.RT.load(RT.java:427)
at clojure.lang.RT.load(RT.java:398)
at clojure.core$load$fn__4610.invoke(core.clj:5386)
at clojure.core$load.doInvoke(core.clj:5385)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5200)
at clojure.core$load_lib.doInvoke(core.clj:5237)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invoke(core.clj:602)
at clojure.core$load_libs.doInvoke(core.clj:5271)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:602)
at clojure.core$require.doInvoke(core.clj:5352)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at user$eval5$fn__7.invoke(form-init5826435134428138571.clj:1)
at user$eval5.invoke(form-init5826435134428138571.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6465)
at clojure.lang.Compiler.eval(Compiler.java:6455)
at clojure.lang.Compiler.load(Compiler.java:6902)
at clojure.lang.Compiler.loadFile(Compiler.java:6863)
at clojure.main$load_script.invoke(main.clj:282)
at clojure.main$init_opt.invoke(main.clj:287)
at clojure.main$initialize.invoke(main.clj:315)
at clojure.main$null_opt.invoke(main.clj:348)
at clojure.main$main.doInvoke(main.clj:426)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:405)
at clojure.lang.AFn.applyToHelper(AFn.java:163)
at clojure.lang.Var.applyTo(Var.java:518)
at clojure.main.main(main.java:37)
Caused by: java.lang.RuntimeException: Unable to resolve symbol: ex-info in this context
at clojure.lang.Util.runtimeException(Util.java:156)
at clojure.lang.Compiler.resolveIn(Compiler.java:6720)
at clojure.lang.Compiler.resolve(Compiler.java:6664)
at clojure.lang.Compiler.analyzeSymbol(Compiler.java:6625)
at clojure.lang.Compiler.analyze(Compiler.java:6198)
... 76 more
It looks like that symbol is actually present at that line, but I'm not sure why it's only sometimes unresolved.
lein version
says Leiningen 2.5.3 on Java 1.7.0-u60-unofficial OpenJDK 64-Bit Server VM
, in case it matters.
When using, say, let-programs, the command line parameters to the underlying script must be specified as regular function arguments:
(let-programs [ls "/bin/ls"] (ls "-l" "-1" "--no-color" "-r"))
I need to build the parameter list for ls dynamically, but have not found a way to do so. None of seq, array, list, ... is working. From what i can see from reading the macro source, it is a variable function argument, but i do not know enough clojure (macro?) voodoo to build a function arglist programmatically.
Would it be possible to enhance the macros to support some sort of list-like parameter? Something along the line of
(let-programs [ls "/bin/ls"] (ls ["-l" "-1" "--no-color" "-r"]))
Other than that: thanks for the wonderful lib!
Kill them mean old processes if they take too long.
I'm trying to use conch and invoke the start-stop-daemon command on debian.
(start-stop-daemon
"--start -m --pidfile /var/run/Sample.develop.pid --background --exec /usr/bin/mono -- /tmp/deployBot/staging/Sample/develop/bin/Debug/Sample.exe")
However, it seems conch is surrounding my args in single quotes:
:stderr "start-stop-daemon: unrecognized option '--start -m
--pidfile /var/run/Sample.develop.pid --background --exec /usr/bin/mono
-- /tmp/deployBot/staging/Sample/develop/bin/Debug/Sample.exe'
Try 'start-stop-daemon --help' for more information."
Am I using conch incorrectly? Is there some way to circumvent this issue?
Would this be an acceptable solution to the issue raised at
https://github.com/Raynes/conch/blob/master/src/me/raynes/conch/low_level.clj#L102
I think it's a better tradeoff than System/out, since output can show up in tooling repls such as cider.
This seems related to #10, but as a specific example I wonder whether this can be made to work properly. I repeat the example from the docs to show that cat
is working, but python
behaves differently. Can an interactive program be made to work with Conch?
flowrweb.core=> (def p (sh/proc "cat"))
#'flowrweb.core/p
flowrweb.core=> (future (sh/stream-to-out p :out))
#future[{:status :pending, :val nil} 0x6e1e2ba]
flowrweb.core=> (sh/feed-from-string p "foo\n")
foo
nil
flowrweb.core=> (def py (sh/proc "python"))
#'flowrweb.core/py
flowrweb.core=> (future (sh/stream-to-out py :out))
#future[{:status :pending, :val nil} 0x5645b83]
flowrweb.core=> (sh/feed-from-string py "1+1\n")
nil
flowrweb.core=> (future (sh/stream-to-out py :err))
#future[{:status :pending, :val nil} 0x455548ec]
flowrweb.core=> (sh/feed-from-string py "1+1\n")
nil
flowrweb.core=>
Hello! This lib is wonderful, I am having a hard time in passing a sequence of parameters though. It looks like it is supported...I tried many combinations but I cannot have this working:
Some of the attemps:
(docker (seq login-params) {:verbose true :throw true}])
(docker {:in (seq login-params) :verbose true :throw true}])
(apply docker (seq login-params) [{:verbose true :throw true}])
It looks like I am missing something. The input to seq
is a vector of strings so my seq
here is just to show that it is actually a sequence I want to pass in.
https://groups.google.com/forum/m/#!topic/clojure/hiX7QrTec3A
Please see discussion. The library is working fine for me but not Tim. Hope you can figure out the cause of this.
It is not obvious from the documentation how to pass options to a command. For example, having tested a command like find . -maxdepth 1 -type f
in the shell, I define a function
(defn some-file-names [dir]
(with-programs [find]
(let [path dir]
(find path "-maxdepth 1" "-type f" {:seq true}))))
which fails. I didn't get anywhere with the low-level api, either. I don't know whether this is missing functionality, or merely absent documentation.
Tested with nexe, which places a *.cmd script at: C:\Users\benzap\AppData\Roaming\npm\nexe.cmd
This is installed with npm install nexe -g
While in cmd.exe:
C:\Users\benzap>where nexe
C:\Users\benzap\AppData\Roaming\npm\nexe
C:\Users\benzap\AppData\Roaming\npm\nexe.cmd
C:\Users\benzap>node -e "console.log('hello')"
hello
C:\Users\benzap>nexe --version
2.0.0-rc.26
build.boot
(set-env!
:resource-paths #{"src" "resources"}
:dependencies '[[org.clojure/clojure "1.8.0"]
[me.raynes/conch "0.8.0"]])
(require '[me.raynes.conch :refer [programs with-programs let-programs] :as sh])
;; Does not work
(deftask hello-nexe []
(with-programs [nexe]
(println (nexe "--version"))
))
;; Works
(deftask hello-node []
(with-programs [node]
(println
(node "-e" "console.log('hello')"))))
Output
C:\Users\benzap\projects\cljs-nexe>boot hello-node
hello
C:\Users\benzap\projects\cljs-nexe>boot hello-nexe
java.lang.Thread.run
java.util.concurrent.ThreadPoolExecutor$Worker.run
java.util.concurrent.ThreadPoolExecutor.runWorker
java.util.concurrent.FutureTask.run
...
clojure.core/binding-conveyor-fn/fn core.clj: 1938
boot.core/boot/fn core.clj: 1032
...
boot.core/construct-tasks core.clj: 994
clojure.core/apply core.clj: 646
...
boot.user$eval8078$fn__8079.doInvoke : 21
...
boot.user$eval8078$fn__8079$nexe__8084.doInvoke : 20
clojure.core/apply core.clj: 648
...
me.raynes.conch/execute conch.clj: 236
me.raynes.conch/execute conch.clj: 245
me.raynes.conch/run-command conch.clj: 197
clojure.core/apply core.clj: 648
...
me.raynes.conch.low-level/proc low_level.clj: 7
me.raynes.conch.low-level/proc low_level.clj: 33
java.lang.ProcessBuilder.start
java.lang.ProcessImpl.start
java.lang.ProcessImpl.<init>
java.lang.ProcessImpl.create
java.io.IOException: CreateProcess error=2, The system cannot find the file specified
java.io.IOException: Cannot run program "nexe": CreateProcess error=2, The system cannot find the file specified
clojure.lang.ExceptionInfo: Cannot run program "nexe": CreateProcess error=2, The system cannot find the file specified
line: 30
C:\Users\benzap\projects\cljs-nexe>
Currently if you use conch.low-level, you can pass {:clear-env true} as an option. In the high level interface, all keys except :dir, :env, and :redirect-err are stripped before being passed to conch/proc. If add-proc-args allowed :clear-env to pass though, it would be helpful.
hello Antony,
just wanted to thank you for conch, clean and smart!
(feel free to close this "issue" as soon as you read it :-)
I'm new to Clojure and using conch to feed output from my program to feedgnuplot to track its progress.
First I make a 'plotter' using the low-level sh/proc:
(:require [me.raynes.conch.low-level :as sh])
(defn make-plotter
"Returns a plotter ready to plot!"
[]
(sh/proc "feedgnuplot" "--stream"))
And I feed data to it using sh/feed-from-string:
(defn plot-point
"Adds a point to a plotter"
[plotter point]
(sh/feed-from-string plotter (str point "\n")))
However I get the following error:
IOException Stream closed java.lang.ProcessBuilder$NullOutputStream.write (ProcessBuilder.java:433)
Is this a bug? Or am I doing something wrong?
Thanks in advance for taking a look!
Hi,
Would be super helpful to have some brief description of the options in the README. I didn't realise i could just use {:background true}
to background the process instead of a messy (sh "bash" "-c" "blah &")
type function, until i stumbled across it browsing the code.
Kind regards,
Brett
p.s. thanks for the library, super clean way to call shell processes.
Hi @Raynes :)
I was wondering if I've been using the conch
library properly with the API clients like HTTPIE
or CURL
. I really like this library and have used it a lot in previous projects for shelling-out
but recently when I tried this with API clients it seems to hang emacs
- perhaps by waiting for the response for too long.
Could you tell me more regarding it's usage with these shell utilities ?
Thanks for this library though ๐
I think Drinkable should be defined on InputStream exactly the same as it is defined on Reader.
If this makes sense to you I'll send a pull request :).
This library is pretty great, but not being able to use it with babashka is kind of a bummer, these two sound like a perfect combination (altough their implementations may not be so compatible). Are there any plans on adding babashka support for this project?
When I call (programs echo)
, I get the following error:
NoClassDefFoundError me/raynes/conch$programs$iter__8799__8803 me.raynes.conch/programs
I've imported conch as such:
(require '[me.raynes.conch :refer [programs with-programs let-programs] :as sh])
Hi!
I need to run a command from within clojure that does two things (one essential, one nice) that require the command to have inherited the process I/O.
Here's the code that I'd love to replace with conch (but can't right now):
(defn ^:private realtime-output-sh
"A version of sh in which the child process inherits I/O."
[& args]
(let [command-line (take-while string? args)
{:keys [dir env]} (->> args
(drop-while string?)
(apply hash-map))
process-builder (let [pb (doto (ProcessBuilder. command-line)
(.inheritIO)
(.directory (io/file dir)))]
(.clear (.environment pb))
(doseq [[env-name env-value] env]
(.put (.environment pb) env-name env-value))
pb)
process (.start process-builder)
exit-code (.waitFor process)]
exit-code))
I'm not sure what the best way to do this would be. Perhaps allowing :inherit
as a value for :in
, :out
, and :err
?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.