GithubHelp home page GithubHelp logo

memoryfilesystem's Introduction

Memory File System OpenSSF Scorecard Maven Central Javadocs

An in memory implementation of a JSR-203 (Java 7) file system for testing purposes.

<dependency>
    <groupId>com.github.marschall</groupId>
    <artifactId>memoryfilesystem</artifactId>
    <version>2.8.0</version>
</dependency>

Versions 2.x require Java 8+, versions 1.x require Java 7+.

ToC

Supported

  • SeekableByteChannel
  • FileChannel
  • AsynchronousFileChannel
  • InputStream
  • OutputStream
  • BasicFileAttributeView, BasicFileAttributes
  • DosFileAttributeView, DosFileAttributes
  • PosixFileAttributeView, PosixFileAttributes
  • UserDefinedFileAttributeView
  • FileLock
  • DirectoryStream
  • PathMatcher
    • glob
    • regex
  • StandardCopyOption
    • REPLACE_EXISTING
    • COPY_ATTRIBUTES
    • ATOMIC_MOVE
  • StandardOpenOption
    • READ
    • WRITE
    • TRUNCATE_EXISTING
    • CREATE
    • DELETE_ON_CLOSE
  • symbolic links
  • symbolic link loop detection
  • hard links
  • switching the current user
  • switching the current group
  • DOS access checks
  • POSIX access checks
  • umask
  • java.net.URL starting with version 2.6.0. Requires any of the following actions
    • Add -Djava.protocol.handler.pkgs=com.github.marschall.memoryfilesystem command line parameter
    • Call URL.setURLStreamHandlerFactory(new MemoryURLStreamHandlerFactory())

Not Supported

  • FileChannel#map, MappedByteBuffer has final methods that call native methods
  • SecureDirectoryStream
  • WatchService
  • FileTypeDetector, has to be accessible by system classloader
  • faked DOS attribute view under Linux, totally unspecified
  • UnixFileAttributeView, sun package, totally unspecified
  • AclFileAttributeView
  • files larger than 16MB
  • StandardOpenOption
    • SPARSE
    • SYNC
    • DSYNC
  • maximum path length checks
  • hard link count checks

Version History

Version 2 requires Java 8 and supports nanosecond time resolution. Automatically set mtime, atime and ctime will have nanosecond resolution only with Java 9+.

Version 1 requires Java 7.

FAQ

Does it have bugs?

Quite likely.

What license is it?

MIT

Does it support concurrent access?

Yes, but hasn't been subject to much scrutiny so bugs are likely.

Does it work with the zipfs provider?

It should work fine in JDK 8+.

Is it production ready?

No, it's only intended for testing purposes.

Does it scale?

No

Does it have any dependencies?

No

Does it support JDK 9?

Yes, starting from version 0.9.2 the JAR is a modular JAR with the name com.github.marschall.memoryfilesystem. The only module required besides java.base is java.annotation which is optional.

Does it work with Spring?

Yes, there is a POJO factory bean. It has been tested with Spring 3.2.4 but since it doesn't have any dependencies on Spring it should work with every ⩾ 2.x version. You can of course also use Java configuration or any other IoC container.

Does it work with OSGi?

Yes, it's a bundle and there's an activator that prevents class loader leaks. You should use the MemoryFileSystemBuilder instead of FileSystems#newFileSystem because ServiceLoader uses the thread context class loader. MemoryFileSystemBuilder avoids this by passing in the correct class loader.

Does it do any logging?

No

But I want all my file access logged

A logging file system that wraps an other file system is the best way to do this.

How can I set the current user?

Use CurrentUser#useDuring

How can I set the current group?

Use CurrentGroup#useDuring

Can I run Lucene?

Yes, starting with version 2.1 running Lucene is supported, see LuceneRegressionTest. It is important you use the #newLinux() method on MemoryFileSystemBuilder.

Are there other similar projects?

Yes, google/jimfs, sbridges/ephemeralfs, pbzdyl/memoryfs, sylvainjuge/memoryfs, twh270/jmemfs and nidi3/j7sf seem similar.

How does this compare to ShrinkWrap NIO.2?

ShrinkWrap NIO.2 seems to be mainly targeted at interacting with a ShrinkWrap archive instead of simulating a file system.

Usage

Getting Started

The easiest way to get started is to use the MemoryFileSystemBuilder

try (FileSystem fileSystem = MemoryFileSystemBuilder.newEmpty().build()) {
  Path p = fileSystem.getPath("p");
  System.out.println(Files.exists(p));
}

It's important to know that at any given time there can only be one memory file system with a given name. Any attempt to create a memory file system with the name of an existing one will throw an exception.

There are other new methods on MemoryFileSystemBuilder that allow you to create different file systems and other methods that allow you to customize the file system.

Next Steps JUnit 4

You probably want to create a JUnit TestRule that sets up and tears down a file system for you. A rule can look like this

final class FileSystemRule implements TestRule {

  private FileSystem fileSystem;

  FileSystem getFileSystem() {
    return this.fileSystem;
  }

  @Override
  public Statement apply(final Statement base, Description description) {
    return new Statement() {

      @Override
      public void evaluate() throws Throwable {
        fileSystem = MemoryFileSystemBuilder.newEmpty().build();
        try {
          base.evaluate();
        } finally {
          fileSystem.close();
        }
      }

    };
  }

}

and is used like this

public class FileSystemTest {

  @Rule
  public final FileSystemRule rule = new FileSystemRule();

  @Test
  public void lockAsyncChannel() throws IOException {
    FileSystem fileSystem = this.rule.getFileSystem();

    Path path = fileSystem.getPath("sample.txt");
    assertFalse(Files.exists(path));
  }

}

It's important to note that the field holding the rule must be public.

Next Steps JUnit 5

You probably want to create a JUnit extension that sets up and tears down a file system for you. A rule can look like this

class FileSystemExtension implements BeforeEachCallback, AfterEachCallback {

  private FileSystem fileSystem;

  FileSystem getFileSystem() {
    return this.fileSystem;
  }

  @Override
  public void beforeEach(ExtensionContext context) throws Exception {
    this.fileSystem = MemoryFileSystemBuilder.newEmpty().build("name");
  }

  @Override
  public void afterEach(ExtensionContext context) throws Exception {
    if (this.fileSystem != null) {
      this.fileSystem.close();
    }
  }
}

and is used like this

class FileSystemTest {

  @RegisterExtension
  final FileSystemExtension extension = new FileSystemExtension();

  @Test
  public void lockAsyncChannel() throws IOException {
    FileSystem fileSystem = this.extension.getFileSystem();

    Path path = fileSystem.getPath("sample.txt");
    assertFalse(Files.exists(path));
  }

}

If you're using an IoC container for integration tests check out the section below.

Spring

The com.github.marschall.memoryfilesystem.MemoryFileSystemFactoryBean provides integration with Spring.

  <bean id="memoryFileSystemFactory"
      class="com.github.marschall.memoryfilesystem.MemoryFileSystemFactoryBean"/>

  <bean id="memoryFileSystem" destroy-method="close"
    factory-bean="memoryFileSystemFactory" factory-method="getObject"/>

You can of course also write a Java Configuration class and a @Bean method that uses MemoryFileSystemBuilder to create a new file system. Or a CDI class with a @Produces method that uses MemoryFileSystemBuilder to create a new file system.

By setting the "type" attribute to "windows", "linux" or "macos" you can control the semantics of the created file system.

For more information check out the Javadoc.

Guidelines for Testable File Code

The following guidelines are designed to help you write code that can easily be tested using this project. In general code using the old File API has to moved over to the new Java 7 API.

  • Inject a Path or FileSystem instance into the object doing the file handling. This allows you to pass in an instance of a memory file system when testing and an instance of the default file system when running in production. You can always the the file system of a path by using Path#getFileSystem().
  • Don't use File, FileInputStream, FileOutputStream, RandomAccessFile and Path#toFile(). These classes are hard wired to the default file system.
    • Use Path instead of File.
    • Use SeekableByteChannel instead of RandomAccessFile. Use Files#newByteChannel to create an instance of SeekableByteChannel.
    • Use Files#newInputStream and Files#newOutputStream to create InputStreams and OutputStreams on files.
    • Use FileChannel#open instead of FileInputStream#getChannel(), FileOutputStream#getChannel(), or RandomAccessFile#getChannel() to create a FileChannel
  • Use FileSystem#getPath(String, String...) instead of Paths#get(String, String...) to create a Path instance because the latter creates an instance on the default file system.

Building

The project requires that JAVA_HOME is set to a JDK 11 or a toolchain with version 11 is set up.

memoryfilesystem's People

Contributors

ascopes avatar ckiosidis avatar dependabot[bot] avatar eyalkaspi-delphix avatar ferstl avatar fge avatar glhez avatar guicamest avatar jlmuir avatar kriegaex avatar marschall avatar rmohr 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

memoryfilesystem's Issues

java 8 BlockChannel.read problem

environment: osx 10.9 java 8 (1.8.0), memoryfilesystem 0.5.3

    FileSystem fs = MemoryFileSystemBuilder.newEmpty().build("53");
    Path path = fs.getPath("one").toAbsolutePath();
    Files.write( path, "hallo world".getBytes("UTF-8"));
    Files.readAllBytes(path);

produces

Exception in thread "main" java.nio.channels.NonReadableChannelException
at     com.github.marschall.memoryfilesystem.BlockChannel.readCheck(BlockChannel.java:65)
at com.github.marschall.memoryfilesystem.BlockChannel.readLock(BlockChannel.java:75)
at com.github.marschall.memoryfilesystem.BlockChannel.read(BlockChannel.java:82)
at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:65)
at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:109)
at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:103)
at java.nio.file.Files.read(Files.java:3102)
at java.nio.file.Files.readAllBytes(Files.java:3155)
at org.opencage.lindwurm.niotest.Marschal53Test.main(Marschal53Test.java:21)

this works fine in the latest java 7.

Can not set posix permissions if there were no "WRITE" access at creation

Hello,

Using the following code:

  @Test
  public void test() throws IOException {
    try (FileSystem fs = MemoryFileSystemBuilder.newEmpty()
        .addFileAttributeView(DosFileAttributeView.class)
        .addFileAttributeView(PosixFileAttributeView.class)
        .build("perm")) {

      final Path path = fs.getPath("readable-at-first");
      Files.createFile(path,  PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("r--r--r--")));

      assertThat(Files.isReadable(path)).isTrue(); // ok
      assertThat(Files.isWritable(path)).isFalse(); // ok
      assertThat(Files.isExecutable(path)).isFalse(); // ok

      Files.setPosixFilePermissions(path, PosixFilePermissions.fromString("rw-r--r--")); // FAIL.

      assertThat(Files.isReadable(path)).isTrue(); // ok
      assertThat(Files.isWritable(path)).isTrue(); // (should be) ok
      assertThat(Files.isExecutable(path)).isFalse(); // ok
    }
  }

I get this exception:

java.nio.file.AccessDeniedException
    at com.github.marschall.memoryfilesystem.MemoryEntry$MemoryPosixFileAttributeView.checkAccess(MemoryEntry.java:810)
    at com.github.marschall.memoryfilesystem.MemoryEntry.checkAccess(MemoryEntry.java:168)
    at com.github.marschall.memoryfilesystem.MemoryEntry$MemoryPosixFileAttributeView.setPermissions(MemoryEntry.java:788)

This fails here:

    @Override
    public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
      if (perms == null) {
        throw new IllegalArgumentException("permissions must not be null");
      }
      try (AutoRelease lock = this.entry.writeLock()) {
        this.entry.checkAccess(AccessMode.WRITE); // <--- HERE.
        this.permissions = toMask(perms);
      }
    }

I think the check should not verify for the current permissions, but simply if the current owner is the file's owner, like if I were calling chmod.

On the other hand: delete the file works.

Files.getFileAttributeView throws UnsupportedOperationException

Hello.

The documentation for Files.getFileAttributeView states that the method returns null for unsupported views:

http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#getFileAttributeView%28java.nio.file.Path,%20java.lang.Class,%20java.nio.file.LinkOption...%29

So, if I create a memory filesystem and don't specify a PosixFileAttributeView to the builder, I would expect the above method to return null if I attempt:

    final PosixFileAttributeView posix_view = Files.getFileAttributeView(
      root, PosixFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);

However, I get UnsupportedOperationException instead. This makes it somewhat less than useful for checking that code properly handles non-posix filesystems during testing.

java.lang.UnsupportedOperationException: file attribute viewinterface java.nio.file.attribute.PosixFileAttributeView not supported
    at com.github.marschall.memoryfilesystem.MemoryEntry.getFileAttributeView(MemoryEntry.java:256)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem$8.value(MemoryFileSystem.java:633)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem$8.value(MemoryFileSystem.java:629)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem.withLockDo(MemoryFileSystem.java:766)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem.withReadLockDo(MemoryFileSystem.java:725)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem.accessFile(MemoryFileSystem.java:675)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem.accessFileReading(MemoryFileSystem.java:663)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem.getFileAttributeView(MemoryFileSystem.java:629)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem$LazyFileAttributeView.getView(MemoryFileSystem.java:1415)
    at com.github.marschall.memoryfilesystem.MemoryFileSystem$LazyFileAttributeView.invoke(MemoryFileSystem.java:1403)

Use long instead of FileTime to store time

FileTime objects can be quite large compared to a long. We can get a way with storing just a long and "expanding" to FileTime when reading the attributes.

This is made possible by the refactoring for #16

BasicFileAttributes are seen as not supported PosixFileAttrributes

e.g. somepath.getFileSystem().provider.setAttribute( file, "basic:lastModifiedTime", sometime );

throws java.lang.UnsupportedOperationException: file attribute viewinterface java.nio.file.attribute.PosixFileAttributeView not supported

found in 0.6.2
worked in 0.5.4

Note: Test ran on win 7

Fix Symlink Copy Semantics

REPLACE_EXISTING
If the target file exists, then the target file is replaced if it is not a non-empty directory. If the target file exists and is a symbolic link, then the symbolic link itself, not the target of the link, is replaced.
NOFOLLOW_LINKS
Symbolic links are not followed. If the file is a symbolic link, then the symbolic link itself, not the target of the link, is copied. It is implementation specific if file attributes can be copied to the new link. In other words, the COPY_ATTRIBUTES option may be ignored when copying a symbolic link.

can't create a file in a sym linked directory

    Path parent = FS.getPath( "linkParent").toAbsolutePath();
    Files.createDirectories( parent );
    Path target = FS.getPath( "target").toAbsolutePath();
    Files.createDirectories( target );
    Files.createSymbolicLink( parent.resolve("link"), target );

    Files.write( parent.resolve("link").resolve("kid"), "hallo".getBytes("UTF-8"));

throws:
java.nio.file.NotDirectoryException: /linkParent/link
at com.github.marschall.memoryfilesystem.MemoryFileSystem$11.value(MemoryFileSystem.java:670)
at com.github.marschall.memoryfilesystem.MemoryFileSystem.withLockDo(MemoryFileSystem.java:724)
at com.github.marschall.memoryfilesystem.MemoryFileSystem.withWriteLockOnLastDo(MemoryFileSystem.java:665)
at com.github.marschall.memoryfilesystem.MemoryFileSystem.getFile(MemoryFileSystem.java:321)
at com.github.marschall.memoryfilesystem.MemoryFileSystem.newOutputStream(MemoryFileSystem.java:295)
at com.github.marschall.memoryfilesystem.MemoryFileSystemProvider.newOutputStream(MemoryFileSystemProvider.java:266)

Add Version of Deploy Plugin

    [WARNING] 
    [WARNING] Some problems were encountered while building the effective model for com.github.marschall:memoryfilesystem:bundle:0.5.4
    [WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-deploy-plugin is missing.
    [WARNING] 
    [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
    [WARNING] 
    [WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
    [WARNING]

Files Not Created By Default

From the documentation of Files.newBufferedWriter:

If no options are present then this method works as if the CREATE, TRUNCATE_EXISTING, WRITE options are present.

I believe the problem should be addressed here: https://github.com/marschall/memoryfilesystem/blob/master/src/main/java/com/github/marschall/memoryfilesystem/MemoryFileSystem.java#L268

This is very easy to test by doing:

try (BufferedWriter writer = Files.newBufferedWriter(fs.getPath("/foo/bar/baz.txt"))) {
  writer.write('c');
}

Closing an InputStream twice causes subsequent reads to fail with a NoSuchFileException

This code fails:

    import java.nio.file.*; import java.io.*;
    @Test
    public void testMemoryFileSystemMultipleClose() {
      FileSystem fs = MemoryFileSystemBuilder.newLinux().build("ID");
      Path foo = fs.getPath("foo");

      Files.write(foo, java.util.Collections.singleton("Foo"));

      InputStream is = Files.newInputStream(foo);
      is.close();
      is.close(); //remove the second call to close() and all is OK

      InputStream is2 = Files.newInputStream(foo); //NoSuchFileException
    }

This is because the openCount of a MemoryFile being less than zero is an indication that the file does not exist. I would suggest one of two fixes.

First option: BlockInputStream keeps track of whether it has been closed; closing an already-closed stream is a no-op. This seems to be the behaviour of the standard library's streams (as I have found this "bug" in code which works in reality!)

    @Override
    public void close() throws IOException {
      if (this.checker.isOpen()) {
        this.checker.close();
        this.memoryContents.modified();
        this.memoryContents.closedStream(this.path, this.deleteOnClose);
      }
    }

Second option: closing a stream/channel on a Path should not let the openCount drop below zero. that is

    this.openCount = Math.max(this.openCount - 1, 0)

However, I have not tested either changes

Case-insensitive setting loses case information?

Hello!

I realize the title of the ticket is a little strange, but I feel that the "case-insensitive" setting of the memory filesystem may be too strong. That is, when enabling a case insensitive filesystem via MemoryFileSystemBuilder.setCaseSensitive(false), the case of the original filename appears to be lost. Rather than just perform comparisons of filenames in a case insensitive manner, it seems that all names are translated to uppercase before being written to "disk".

Why does this matter? Let's say you're writing a program to catalog disks. You walk a filesystem, reading filenames and metadata into a graph/tree structure (effectively, a read-only filesystem that does not store actual file data). In order to allow the graph/tree structure to be filesystem-agnostic, the names in the tree structure have to be case sensitive. If the original filesystem was case insensitive, there is no problem, because files FILE0.TXT and FiLe0.TxT cannot both be in the same directory. If the original filesystem was case sensitive, there is no problem because the graph/tree representation is also case sensitive.

The user creates a file called File0.txt on a case insensitive filesystem. The user then runs the catalog program on this filesystem. Later, the user tries to open File0.txt in the catalog: Uh oh! The filesystem quietly converted the name to FILE0.TXT when the catalog was created and the file cannot be found! This leads to subtle problems when writing test suites that are parameterized by filesystems: The tests have to always assume completely uppercase filenames to be able to check for the same results across filesystems. On a real case-insensitive filesystem such as NTFS, this does not happen.

I believe the correct behaviour should be to return filenames using the case that was used when they were created, but to do case-insensitive string comparisons when it becomes time to compare names.

BlockInputStream.read does not return an Int in the range -1..255

This line is wrong: https://github.com/marschall/memoryfilesystem/blob/master/src/main/java/com/github/marschall/memoryfilesystem/BlockInputStream.java#L131

If you compare to java.io.ByteArrayInputStream which also reads a byte from a byte array, the byte should have a & 0xff in order to return as an int in the specified range.

that is, the method should be:

@Override
public int read() throws IOException {
  byte[] data = new byte[1];
  int read = this.read(data);
  if (read == -1) {
    return read;
  } else {
    return data[0] & 0xff;
  }
}

Files.readAllLines() does not handle symlinks

If I create a symlink to a file, then attempt to call Files.readAllLines() on the symlink, I get the following crash:

  at com.github.marschall.memoryfilesystem.MemoryFileSystem$1.value(MemoryFileSystem.java:332)
  at com.github.marschall.memoryfilesystem.MemoryFileSystem$1.value(MemoryFileSystem.java:299)
  at com.github.marschall.memoryfilesystem.MemoryFileSystem$11.value(MemoryFileSystem.java:592)
  at com.github.marschall.memoryfilesystem.MemoryFileSystem.withLockDo(MemoryFileSystem.java:612)
  at com.github.marschall.memoryfilesystem.MemoryFileSystem.withWriteLockOnLastDo(MemoryFileSystem.java:585)
  at com.github.marschall.memoryfilesystem.MemoryFileSystem.getFile(MemoryFileSystem.java:299)
  at com.github.marschall.memoryfilesystem.MemoryFileSystem.newInputStream(MemoryFileSystem.java:260)
  at com.github.marschall.memoryfilesystem.MemoryFileSystemProvider.newInputStream(MemoryFileSystemProvider.java:247)
  at java.nio.file.Files.newInputStream(Files.java:152)
  at java.nio.file.Files.newBufferedReader(Files.java:2781)
  at java.nio.file.Files.readAllLines(Files.java:3199)

Here is a reproduction:

private static void test() throws IOException {
    final FileSystem fileSystem = MemoryFileSystemBuilder.newLinux().build("1");

    final List<String> lines = new ArrayList<>();
    lines.add("Hello world");

    final Path filePath = fileSystem.getPath("/").resolve("file");
    Files.write(filePath, lines, StandardCharsets.UTF_8);

    final Path linkPath = filePath.resolveSibling("link");
    Files.createSymbolicLink(linkPath, filePath);

    final List<String> lines1 = Files.readAllLines(linkPath, StandardCharsets.UTF_8);
    System.out.println(lines1);
}

I believe the problem is here, where there is a third case of MemorySymbolicLink that is not being handled:

      if (storedEntry instanceof MemoryFile) {
        return (MemoryFile) storedEntry;
      } else {
        throw new IOException("file is a directory");
      }

The meaning of umask when a Linux fs is created is inverted

When you use .setUmask() on the Linux memory fs builder, it sets the mask applied to all file creations as in:

mode | umask

But this is not the meaning of an umask! u is for Undo, it is the set of permissions to be removed from each mode:

mode & ~umask

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.