amasad / sane Goto Github PK
View Code? Open in Web Editor NEWsane aims to be fast, small, and reliable filesystem watcher. No bells and whistles, just change events.
sane aims to be fast, small, and reliable filesystem watcher. No bells and whistles, just change events.
Issue filed here https://github.com/facebook/fb-flo/issues/13
We may want to have a timeout + useful error message for users that maybe directs them to: facebook/watchman#358 or some other troubleshooting resource
On Windows, when a watched directory is deleted, an EPERM error is thrown when attempting to stat the change. This can be observed by running the test suite on Windows:
> [email protected] test 'C:\path\to\sane'
> mocha --bail
....................
19 passing (26s)
1 failing
1) sane in normal mode sane(file) removing a dir will emit delete event:
Uncaught Error: EPERM, stat 'C:\temp\path\to\sane_test\sub_9\file_0'
This happens because fs.stat
on win32 will receive an EPERM
error instead of ENOENT
when attempting to stat a non-existent directory. As far as I can tell, this is similar but not related to nodejs/node-v0.x-archive#4337.
A pull request fixing the issue will be forthcoming.
here's what i have so far:
Jonathans-MacBook-Pro:test jong$ DEBUG=component-watcher component build -w
component-watcher watching globs: component.json, index.js, lib/**/*.js, lib/**/*.json, lib/**/*.html, lib/**/*.css +0ms
[ 'component.json',
'index.js',
'lib/**/*.js',
'lib/**/*.json',
'lib/**/*.html',
'lib/**/*.css' ]
[ '/Users/jong/Workspace/test',
'/Users/jong/Workspace/test/build',
'/Users/jong/Workspace/test/components',
'/Users/jong/Workspace/test/lib',
'/Users/jong/Workspace/test/components/component',
'/Users/jong/Workspace/test/components/discore',
'/Users/jong/Workspace/test/components/ianstormtaylor',
'/Users/jong/Workspace/test/components/matthewp',
'/Users/jong/Workspace/test/components/visionmedia',
'/Users/jong/Workspace/test/components/yields',
'/Users/jong/Workspace/test/lib/test',
'/Users/jong/Workspace/test/components/component/classes',
'/Users/jong/Workspace/test/components/component/css',
'/Users/jong/Workspace/test/components/component/delegate',
'/Users/jong/Workspace/test/components/component/dom',
'/Users/jong/Workspace/test/components/component/domify',
'/Users/jong/Workspace/test/components/component/each',
'/Users/jong/Workspace/test/components/component/emitter',
'/Users/jong/Workspace/test/components/component/event',
'/Users/jong/Workspace/test/components/component/indexof',
'/Users/jong/Workspace/test/components/component/matches-selector',
'/Users/jong/Workspace/test/components/component/path-to-regexp',
'/Users/jong/Workspace/test/components/component/props',
'/Users/jong/Workspace/test/components/component/query',
'/Users/jong/Workspace/test/components/component/to-function',
'/Users/jong/Workspace/test/components/component/trim',
'/Users/jong/Workspace/test/components/component/type',
'/Users/jong/Workspace/test/components/component/value',
'/Users/jong/Workspace/test/components/component/within-document',
'/Users/jong/Workspace/test/components/discore/closest',
'/Users/jong/Workspace/test/components/ianstormtaylor/to-camel-case',
'/Users/jong/Workspace/test/components/ianstormtaylor/to-no-case',
'/Users/jong/Workspace/test/components/ianstormtaylor/to-space-case',
'/Users/jong/Workspace/test/components/matthewp/keys',
'/Users/jong/Workspace/test/components/matthewp/text',
'/Users/jong/Workspace/test/components/visionmedia/debug',
'/Users/jong/Workspace/test/components/yields/isarray',
'/Users/jong/Workspace/test/components/yields/traverse',
'/Users/jong/Workspace/test/components/component/classes/1.1.2',
'/Users/jong/Workspace/test/components/component/css/0.0.4',
'/Users/jong/Workspace/test/components/component/delegate/0.2.1',
'/Users/jong/Workspace/test/components/component/dom/1.0.5',
'/Users/jong/Workspace/test/components/component/domify/1.2.2',
'/Users/jong/Workspace/test/components/component/each/0.2.2',
'/Users/jong/Workspace/test/components/component/emitter/1.1.2',
'/Users/jong/Workspace/test/components/component/event/0.1.2',
'/Users/jong/Workspace/test/components/component/indexof/0.0.3',
'/Users/jong/Workspace/test/components/component/matches-selector/0.1.1',
'/Users/jong/Workspace/test/components/component/path-to-regexp/v0.1.2',
'/Users/jong/Workspace/test/components/component/props/1.1.2',
'/Users/jong/Workspace/test/components/component/query/0.0.1',
'/Users/jong/Workspace/test/components/component/to-function/2.0.0',
'/Users/jong/Workspace/test/components/component/trim/0.0.1',
'/Users/jong/Workspace/test/components/component/type/1.0.0',
'/Users/jong/Workspace/test/components/component/value/1.1.0',
'/Users/jong/Workspace/test/components/component/within-document/0.0.1',
'/Users/jong/Workspace/test/components/discore/closest/0.1.2',
'/Users/jong/Workspace/test/components/ianstormtaylor/to-camel-case/0.2.1',
'/Users/jong/Workspace/test/components/ianstormtaylor/to-no-case/0.1.0',
'/Users/jong/Workspace/test/components/ianstormtaylor/to-space-case/0.1.2',
'/Users/jong/Workspace/test/components/matthewp/keys/0.0.3',
'/Users/jong/Workspace/test/components/matthewp/text/0.0.2',
'/Users/jong/Workspace/test/components/visionmedia/debug/0.7.4',
'/Users/jong/Workspace/test/components/yields/isarray/1.0.0',
'/Users/jong/Workspace/test/components/yields/traverse/0.1.1',
'/Users/jong/Workspace/test/components/component/css/0.0.4/lib',
'/Users/jong/Workspace/test/components/component/dom/1.0.5/lib' ]
i didn't specify build
or components
to be watched, but they are being watched anyways, creating an infinite loop for my build process :(
watch(__dirname, ['index.js'])
for example
Just wondering if this is possible. In chokidar you can pass the directory argument as an array...from the api documentation is seems like this isn't an option.
glob
option to filter
(and keeping glob
as an alias, for back compatibility)let me know if you agree with one or both of the above, and I'll PR!
I've posted this answer on SO, for a problem I am experiencing myself.
The mentioned hack seem to solve the problem for now. I would like to understand it better though, as the error directory exists (but is not in the dirRegistry) and the error only occurs when I save the file with sublime 3 and not with any other editor.
Any ideas?
Oh, yeah.. I'm running on Win7 64 bit for what is worth ..
I have to get it running as a task inside NPM build scripts.
If it possible ?
npm WARN engine makeerror@1.0.10: wanted: {"node":"0.6.x"} (current: {"node":"1.6.2","npm":"2.7.3"})
npm WARN engine tmpl@1.0.3: wanted: {"node":"0.6.x"} (current: {"node":"1.6.2","npm":"2.7.3"})
I've submitted PR's to the offending dependencies
Once they update, someone (me if I notice it) should update this module accordingly.
I'm glad someone's concerned about reliability with file watching. I've had so many reliability problems with things like webpack's watcher and chokidar. What I'm not seeing here is any notes on when this is reliable and when it isn't. When i see things like "watchmen is probably the most reliable mode" I want to know what that means very specifically. What scenarios can't this library catch?
Seems like trying to shrink-wrap dependencies when sane is included in the project fails in npm@3
with this error message:
$ npm shrinkwrap --dev
npm ERR! Darwin 15.3.0
npm ERR! argv "/Users/sterpe/.nvm/versions/node/v4.3.2/bin/node" "/Users/sterpe/git/Video/vpaid/.node_modules/.bin/npm" "shrinkwrap" "--dev"
npm ERR! node v4.3.2
npm ERR! npm v3.8.1
npm ERR! Problems were encountered
npm ERR! Please correct and try again.
npm ERR! missing: watch@~0.10.0, required by [email protected]
npm ERR!
npm ERR! If you need help, you may report this error at:
npm ERR! <https://github.com/npm/npm/issues>
npm ERR! Please include the following file with any support request:
npm ERR! /Users/sterpe/git/Video/vpaid/npm-debug.log
not sure how i can help. let me know what you need. seems to happen after a while and not immediately. i only can tell because my cpu fans start spinning. it comes up as node
in my activity monitor.
got about the same utilization with gaze, but this is written better and works with added files, so...
i'm on OS X 10.9.2
this isn't a big deal since i only use this in development
This problem came up as part of react-native and jest 0.9.
To repro: make node-haste use the NodeWatcher in jest's HasteResolver, see https://github.com/facebook/jest/blob/master/src/resolvers/HasteResolver.js#L23
Run any JS test on react-native. The tests will pass but afterwards the process won't exit and just hang. end
is being called properly. Make sure not to use watchman, of course :)
Wanted to open this up here to track the issue. This has caused a few bumps on FB's test infra where watching was accidentally used and then timed out the processes. Ideally, even if watching is used there, the process should properly exit :)
a watchman does not age-out watchers yet, some use-cases would prefer a restart to blow away watching state.
I suspect either this project of fb-watchman
needs some tweaking to support this option.
Branching off the discussion about the chokidar watcher in #53, I've been thinking that a plugin API could be useful to let users of sane (e.g. me) use custom watchers without bloating the sane package or adding too much maintenance cost.
My end goal is to be able to use a custom watcher with the RN packager where the pieces are layered like this:
(The packager would also expose a flag to set the watcher implementation.)
At a high level is this something you'd be open to?
I have been having a problem where uncaught lstat EPERM errors are cropping up on Windows for some users when using the node watcher.
While I have not been able to reproduce the error on my own machine, I did find one potential bug that could cause the symptoms I'm seeing:
recReaddir
function (node_watcher.js#L338) calls a library walker
, which returns an EventEmittererror
events on the emitter, so if/when they happen, they get thrown: events.js#L161It's not clear to me what the correct behavior is in this scenario, should it be logic similar to node_watcher.js#L223?
If running the Node native watcher on a folder that contains a large node_modules directory, we want the ability to ignore this folder.
Is there any way in this project (or with the Node native watcher for that matter), to handle use cases like this?
See jspm/jspm-cli#1999 for more info.
The individual watchers NodeWatcher
PollWatcher
etc expect to be used with the new
operator. The sane
function on does that, but switching from var watch = require('sane');
to var watch = require('sane').NodeWatcher;
is not a valid transformation. Can the Watcher ctors be made to instantiate themselves, if ! this instanceof Watcher
?
Calling close
on a watcher closes currently watched directories but does not account for asynchronous callbacks.
Easiest way to reproduce is to create a watcher and try to close it right away.
I have a glob list that looks like:
templates: ['**/*.html', '!**/*index.html'],
(angular templatecache, I don't want to template my index.html, just all the child html). The problem is this watcher actually fires on all file changes - I am 98% sure it's because the !**/*index.html
matches almost all the files in my build tree (including .js files, .scss files, and so on). In other uses of arrays-of-minimatch, the notted conditions tend to be anded together with the regular conditions, like "match when (C1 or C2 or C3) AND (N4 and N5)" for something like ['C1', 'C2', 'C3', '!N4', '!N5'] if that makes sense.
The five most popular dependants of sane
, jest-haste-map
, jest-cli
, ember-cli
, react-native
, and broccoli-sane-watcher
do not use the CLI. It would reduce their dependency graphs to split sane
into two packages, something like sane
and sane-cli
, or sane
and sane-core
. Would something like this be considered?
│ └─┬ [email protected]
│ └── [email protected]
npm WARN deprecated [email protected]: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
Currently, sane is stat'ing both on initial walk (when registering files by way of the walker
callback), and when change events happen (here). It could be very useful to downstream consumers to have access to the result of those fs.stat
calls. I propose, adding the prior stat value to the CHANGE_EVENT
and DELETE_EVENT
events, and the new stat to the ADD_EVENT
.
A super hacky and likely horrible diff showing what kind of thing I am talking about:
diff --git a/index.js b/index.js
index 7811b28..66a7831 100644
--- a/index.js
+++ b/index.js
@@ -113,7 +113,7 @@ Watcher.prototype.isFileIncluded = function(relativePath) {
* @private
*/
-Watcher.prototype.register = function(filepath) {
+Watcher.prototype.register = function(filepath, stat) {
var relativePath = path.relative(this.root, filepath);
if (!this.isFileIncluded(relativePath)) {
return false;
@@ -125,7 +125,7 @@ Watcher.prototype.register = function(filepath) {
}
var filename = path.basename(filepath);
- this.dirRegistery[dir][filename] = true;
+ this.dirRegistery[dir][filename] = stat;
return true;
};
@@ -306,22 +306,22 @@ Watcher.prototype.processChange = function(dir, event, file) {
// win32 emits usless change events on dirs.
if (event !== 'change') {
this.watchdir(fullPath);
- this.emitEvent(ADD_EVENT, relativePath);
+ this.emitEvent(ADD_EVENT, relativePath, stat);
}
} else {
- var registered = this.registered(fullPath);
+ var registeredStat = this.registered(fullPath);
if (error && error.code === 'ENOENT') {
this.unregister(fullPath);
this.stopWatching(fullPath);
this.unregisterDir(fullPath);
- if (registered) {
- this.emitEvent(DELETE_EVENT, relativePath);
+ if (registeredStat) {
+ this.emitEvent(DELETE_EVENT, relativePath, registeredStat, stat);
}
- } else if (registered) {
- this.emitEvent(CHANGE_EVENT, relativePath);
+ } else if (registeredStat) {
+ this.emitEvent(CHANGE_EVENT, relativePath, registeredStat, stat);
} else {
if (this.register(fullPath)) {
- this.emitEvent(ADD_EVENT, relativePath);
+ this.emitEvent(ADD_EVENT, relativePath, stat);
}
}
}
@@ -340,7 +340,7 @@ Watcher.prototype.emitEvent = function(type, file) {
clearTimeout(this.changeTimers[key]);
this.changeTimers[key] = setTimeout(function() {
delete this.changeTimers[key];
- this.emit(type, file);
+ this.emit.apply(this, arguments);
}.bind(this), DEFAULT_DELAY);
};
My particular use-case is that I need to filter certain events out by way of comparing the before/after stat's. Specifically, on Windows when you hardlink a file (unlike on *nix) a change event is triggered in the source directory as well as the destination directory. Using the nlink
count or even the mtime
in the stats would be enough information to know if we should filter the given event.
Thoughts?
my process exits immediately :( can't use sane(dir, globs)
now
To prevent failing PR's from being merged.
I noticed from #3 that there's a deliberate design decision to watch all dirs in case any new files appear that might match the glob. I can see why this would be helpful in some cases, but it breaks things in others, so a choice would be excellent.
var sane = require('sane');
// Wouldn't actually watch anything, but will crash immediately
sane('/Users/nick/', ['**/fdjsaiofjidosafjioadsfjidosa*']);
As far as I can tell, there's no reasonable way to prevent that from crashing, because there are simply too many dirs opened for watching at once (even with a massive ulimit increase).
My request is that a flag be added to make only files matching the glob on startup get watched. My core use case is for fb-flo, where I want to watch projectdir/static/minified/js/**/*.js
and projectdir/static/templates/**/*.html
, but there are simply too many dirs under projectdir/static to open them all at once.
Thanks!
I'm using sane
+ watchman
(via ember-cli
) to watch a directory. As I edit files in those directories, emacs creates "interlock" symlinks with weird contents; this is what ls
reports on one of them:
lrwxrwxrwx 1 sohum sohum 38 Dec 10 15:34 .#menus-test.js -> [email protected]:1418204054
When these files are deleted, sane
crashes on an ENOENT error.
The error appears to be coming from WatchmanWatcher.prototype.handleFileChange
, as it seems to be treating the stat error as a failure instead of a deletion.
Thanks!
Diversity is good, fragmentation is bad. I'm wondering what the advantage of this package are over chokidar, which seems pretty well maintained and, for now, much more popular. Maybe another fs.watch
wrapper isn't actually needed? Or if it is, can you at least document the differences?
In 0.5.4, I could walk the watcher.dirRegistry to see what files had already been found when ready
fired. Is this no longer the case?
Here is our full scss build file:
#!/usr/bin/env node
var fs = require('fs');
var sane = require('sane');
var sass = require('node-sass');
var watcher = new sane.Watcher('./components', {glob: ['**/*.scss'], poll: true});
var buildSass = function () {
sass.render({
file: './components/app.scss',
sourceMap: true,
success: function (css) {
fs.writeFile('./public/public_assets/css/built.css', css, function (error) {
if (error) return console.log(error);
console.log('CSS built');
});
},
error: function (error) {
console.log(error);
}
});
};
if (!fs.existsSync('./public/public_assets/css')) {
fs.mkdirSync('./public/public_assets/css/');
}
watcher.on('ready', function () {
console.log('SCSS being watched');
buildSass();
});
watcher.on('change', function (filepath) {
console.log('file changed', filepath);
buildSass();
});
watcher.on('add', function (filepath) {
console.log('file added', filepath);
buildSass();
});
watcher.on('delete', function (filepath) {
console.log('file deleted', filepath);
buildSass();
});
This line seems to be the issue:
var watcher = new sane.Watcher('./components', {glob: ['**/*.scss'], poll: true});
If poll: true
is removed or set to false the build task does not run when a file is saved even though the watcher.on('ready')
is being fired. When poll: true
is set though it builds on save like it should.
This seems to be causing issues in fb-flo for me. Deleting a folder crashes the fb-flo session due to the following unhandled exception:
C:\SourceCLS\CLS\Src\Dev\fb-flo\node_modules\fb-flo\node_modules\sane\index.js:242
Object.keys(this.dirRegistery[dir]).forEach(function(file, i, arr) {
^
TypeError: Object.keys called on non-object
at Function.keys (native)
at Watcher.detectChangedFile (C:\SourceCLS\CLS\Src\Dev\fb-flo\node_modules\fb-flo\node_modules\sane\index.js:242:10)
at Watcher.normalizeChange (C:\SourceCLS\CLS\Src\Dev\fb-flo\node_modules\fb-flo\node_modules\sane\index.js:276:10)
at FSWatcher.EventEmitter.emit (events.js:98:17)
at FSEvent.FSWatcher._handle.onchange (fs.js:1039:12)
var watcher = sane('path/to/dir', ['**/*.js', '**/*.css']);
doesn't work out of the box. I had to change it to var watcher = sane('path/to/dir', {glob: ['**/*.js', '**/*.css']});
to make it stop reporting changes to .DS_Store.
thoughts?
From this comment.
Trying to watch a file that does not exist with node_watcher
terminates the process. chokidar
handles this by wrapping fs.watch
in a try-catch block and swallowing the error if it's an ENOENT or ENOTDIR.
Does something like that sound okay?
±master → npm install
/
> [email protected] prepublish /Users/aylott/src/amasad:sane
> jshint --config=.jshintrc src/ index.js && mocha --bail
sh: jshint: command not found
±master → npm install
> [email protected] prepublish /Users/aylott/src/amasad:sane
> jshint --config=.jshintrc src/ index.js && mocha --bail
sh: mocha: command not found
When committing to git while watching, the ALL_EVENT is fired on .git/objects and those files are wrongly acted upon (it also causes some recursive actions on my build folder...essentially my globs are ignored for some files). Removing line 314 of node_watcher.js solves this problem and I can't really see what that line is there for in the first place?
Hi-
I wrote a mostly complete ChokidarWatcher for sane.
I can see reasons why you wouldn't want this to be integrated into the main sane
, (npm installing the native modules chokidar depends on can get hairy on certain platforms), but there does seem to be a bunch of interest in combining or having these projects work together, so I wanted to float the idea out there.
The code is here:
https://github.com/exponentjs/sane
The ChokidarWatcher
isn't entirely complete; it can only handle one glob at a time. I can fix that but I don't need it for my own use, so I may not bother unless there is interest in the ChokidarWatcher in the main project.
I don't have a particular agenda or anything that I want this project to do -- just wanted to let you know that a mostly working chokidar backend is out there now in case that leads anywhere good.
Currently, creating and writing to a file in a watched directory with a method like fs.writeFileSync
causes both an add
and a change
event to be fired. Is there a way to config sane
to only trigger the add
event in cases like this? I have a repository demonstrating the issue.
On windows, while using flo I get following error:
path.js:204
throw new TypeError('Arguments to path.join must be strings');
^
TypeError: Arguments to path.join must be strings
at f (path.js:204:15)
at Object.filter (native)
at Object.exports.join (path.js:209:40)
at Watcher.processChange (c:\Workspace\fb-flo-gulp\node_modules\fb-flo\node_modules\sane\index.js:203:23)
at FSWatcher.EventEmitter.emit (events.js:98:17)
at FSEvent.FSWatcher._handle.onchange (fs.js:1039:12)
Using node v0.10.28.
I've debugged a bit:
Watcher.prototype.processChange = function(dir, event, file) {
console.log(dir);
console.log(file);
console.log(event);
output:
c:\Workspace\fb-flo-gulp\app
.subl49b.tmp
rename
c:\Workspace\fb-flo-gulp\app
.subl49b.tmp
change
c:\Workspace\fb-flo-gulp\app
.subl49b.tmp
change
c:\Workspace\fb-flo-gulp\app
.subl49b.tmp
change
c:\Workspace\fb-flo-gulp\app
index.html~RF1c4a5795.TMP
rename
c:\Workspace\fb-flo-gulp\app
null
rename
So it seems that proccessChange receives null files now and then and crashes on these.
mkdir files_watched
for i in {1..1000}; do touch files_watched/$i; done
npm install sane
In node:
var sane = require('sane');
var files = (new Array(1000)).fill().map(function(_, i) { return (i + 1).toString() })
var time = Date.now();
var watcher = sane('.', { glob: files });
watcher.on('ready', function() {
console.log((Date.now() - time)/1000 + " seconds elapsed");
});
Here's what is printed:
22.705 seconds elapsed
What's that? 22 seconds for 1000 files?
What if we just use *
pattern?
var sane = require('sane');
var time = Date.now();
var watcher = sane('.', { glob: '*' });
watcher.on('ready', function() {
console.log((Date.now() - time)/1000 + " seconds elapsed");
});
0.091 seconds elapsed
Wow, that's much better.
What's the case here?
Currently sane doesn't pick up files starting with a dot.
minimatch has a dot: true
option.
Could you either turn this option on in minimatch, or provide a way for me to pass in minimatch options when invoking sane?
this module looks perfect, thank you for writing it.
I'm confused about the persistent
option.. how could watching files ever not be persistent?
The title is just the nuts and bolts of the proposal, my suggestion is much more kind 😄
chokidar (followed closely by gaze) is probably the standard file watcher favored by the node community (at large). I think we can agree the benefits of having one package less to debug / browser for issues whenever a problem occurs.
sane
is the watcher used by ember-cli
so perhaps I should open an issue there instead, but I thought of asking here first and see what you think.
No ill intentions!
Regards
When editing a file (on sublime text 3 + Windows 8.1 64 bits) I got some errors saying
Object.keys(this.dirRegistery[dir]).forEach(function(file, i, arr) {
^
TypeError: Object.keys called on non-object
at Function.keys (native)
at Watcher.detectChangedFile (D:\Sites\site.com\node_modules\fb-flo\node_modules\sane\index.js:249:10)
at Watcher.normalizeChange (D:\Sites\site.com\node_modules\fb-flo\node_modules\sane\index.js:283:10)
at FSWatcher.emit (events.js:98:17)
at FSEvent.FSWatcher._handle.onchange (fs.js:1044:12)
After some investigation I found that Sublime text (or some IDE) creates some tmp file that completely mess up how sane watch files. Even with atomic_save to false it seems to create strange file.
I found a fix doing a test on file but I think it would break things doing this. The problem is this file variable being null for random reasons. I can do a PR if you want but I need to know if I broke something with this edit ^^.
Watcher.prototype.normalizeChange = function(dir, event, file) {
if (!file && file !== null) {
I'm using sane v1.0.2
and want to upgrade to the newest version v1.3.0
. As there is no changelog and nothing mentioned about semantic versioning, can you tell me if there are any braking changes?
Directory structure:
var watcher = sane('example1', { glob: ['**/*.txt'] });
Sane not detect move example2
to example1
.
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.