Git Product home page Git Product logo

reflect's Introduction

Reflec<T>

Typesafe serialization of data

Maven Central (replace ${reflectVersion} with this version)

Build Status

Using the library

Dependencies

Maven

To add the dependency using Maven just add the following section to your dependencies:

<dependency>
    <groupId>de.cubeisland.engine</groupId>
    <artifactId>reflect</artifactId>
    <version>${reflectVersion}</version>
</dependency>

Gradle

To add the dependency using Gradle just add the following line to your dependencies:

compile 'de.cubeisland.engine:reflect:${reflectVersion}'

Dependencies for Android

Using this library with Android is a little more difficult as the snakeyaml dependency doesn't work with Android. Nevertheless you can use it by following these steps:

  1. download the snakeyaml-android.jar from this page: snakeyaml-android

  2. copy the file to your project/module

  3. add the following section to your dependencies:

    • Maven:
    <dependency>
        <groupId>de.cubeisland.engine</groupId>
        <artifactId>reflect</artifactId>
        <version>${reflectVersion}</version>
        <exclusions>
            <exclusion>
                <groupId>org.yaml</groupId>
                <artifactId>snakeyaml</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <artifactId>org.yaml</artifactId>
        <groupId>snakeyaml</groupId>
        <version>1.14</version>
        <scope>system</scope>
        <systemPath>${basedir}/RELATIVE_PATH_TO_SNAKEYAML_ANDROID_JAR</systemPath>
    </dependency>
    • Gradle:
    compile ('de.cubeisland.engine:reflect:${reflectVersion}')
    {
        exclude group: 'org.yaml', module: 'snakeyaml'
    }
    compile files('RELATIVE_PATH_TO_SNAKEYAML_ANDROID_JAR')

(Please note: edit the "RELATIVE_PATH_TO_SNAKEYAML_ANDROID_JAR" part!)

Usage example (in a Bukkit plugin) as a Configuration

public class ExamplePlugin extends JavaPlugin
{
    // Create the Factory
    private Reflector factory = new Reflector();

    private MyConfig config;

    public void onEnable()
    {
        File file = new File(this.getDataFolder(), "config.yml");

        // load the reflected using the factory (this will also save after loading)
        config = factory.load(MyConfig.class, file);

        // at any time you can reload the reflected object
        config.reload(); // this will also save the reflected object!
        // or save the reflected object
        config.save();
    }

    // The Reflected Class
    public class MyConfig extends ReflectedYaml // this is the same as extends Reflected<YamlCodec>
    {
        public transient boolean noSaving; // fields that are transient are ignored

        // the path is generated from the field: lots-of-saving
        public boolean lotsOfSaving = true; // set default values (will be set if not loaded OR field is missing in file)

        @Name("default")
        public String defaultString = "You can define the path with an annotation instead. e.g. if you want to use \"default\"";

        // path will be: sub.section
        @Comment({"You can comment every field.","Even with multiple lines\nand this linebreak works too"})
        public SubSection sub_section; // You do not NEED to set the default here ; it is done automatically

        // You can use sections instead of setting the whole path for every field
        public class SubSection implements Section
        {
            // path will be: sub.section.an-int
            public int anInt = 42;
            // path will be: sub.section.a-long
            public long aLong = 666;
        }
    }
}

reflect's People

Contributors

dependabot[bot] avatar faithcaio avatar jonasdann avatar pschichtel avatar swolf91 avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reflect's Issues

NPE while enabling the log module

A NPE is thrown while enabling the log module if the configuration file does not exist. The cleanup attribute of the configuration is null!

14:10:34 [ERROR] [CubeEngine] [Log] NullPointerException while enabling!
14:10:34 [DEBUG] [CubeEngine] [Log] nulljava.lang.NullPointerException: null
        at de.cubeisland.engine.log.storage.QueryManager.<init>(QueryManager.java:128) ~[na:na]
        at de.cubeisland.engine.log.storage.LogManager.<init>(LogManager.java:65) ~[na:na]
        at de.cubeisland.engine.log.Log.onEnable(Log.java:58) ~[na:na]
        at de.cubeisland.engine.core.module.Module.enable(Module.java:251) ~[cubeengine.jar:na]
        at de.cubeisland.engine.core.module.BaseModuleManager.enableModule(BaseModuleManager.java:391) [cubeengine.jar:na]
        at de.cubeisland.engine.core.module.BaseModuleManager.enableModules(BaseModuleManager.java:409) [cubeengine.jar:na]
        at de.cubeisland.engine.core.bukkit.BukkitCore.onEnable(BukkitCore.java:413) [cubeengine.jar:na]
        at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:217) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:457) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:381) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at org.bukkit.craftbukkit.v1_6_R3.CraftServer.loadPlugin(CraftServer.java:284) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at org.bukkit.craftbukkit.v1_6_R3.CraftServer.enablePlugins(CraftServer.java:266) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at net.minecraft.server.v1_6_R3.MinecraftServer.l(MinecraftServer.java:315) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at net.minecraft.server.v1_6_R3.MinecraftServer.f(MinecraftServer.java:292) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at net.minecraft.server.v1_6_R3.MinecraftServer.a(MinecraftServer.java:252) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at net.minecraft.server.v1_6_R3.DedicatedServer.init(DedicatedServer.java:152) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at net.minecraft.server.v1_6_R3.MinecraftServer.run(MinecraftServer.java:393) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]
        at net.minecraft.server.v1_6_R3.ThreadServerApplication.run(SourceFile:583) [server.jar:git-Bukkit-1.6.4-R2.0-b2918jnks]

14:10:34 [ERROR] [CubeEngine] [Log] Module failed to load.

System for not writing fields with default values to file

I've come in quite a few cases where I wish only the fields that differ from the default value in the class is saved.

For instance, if I have a section that has a type property, and some fields are only relevant to one type, or if I just have a lot of fields, and the default values will be fine for many of them in most cases.

It should still be possible to force some fields to be saved, but that could be done with an annotation. Or we could go the other way and annotate fields that only should be hidden if they have the default value.

Codec specific converters

Currently converters are global for all codecs, but not all file types use the same representation of types.

For example the UUIDConverter:

in a yaml configuration, UUID.toString() makes sense to keep it readable, however in NBT or other binary file formats the string representation would waste a lot of space. For binary formats a custom converter would be needed that converters the UUID in 2 longs or a byte array

Order annotation

I would like to have an order annotation which can be attached to a Section class. The order annotation specifies the order of the paths like it is in Simple

Polymorphic Deserialization

Common libraries in the JSON space tend to fail (hard) at this, so we could greatly improve it.

What we need is a way to detect which concrete implemention to sue for the deserialization process.

Dynamic parts in configurations

Sometimes it is very useful to be able to change a config at runtime. Adding fields mostly.

A nice way would be to add hidden fields in Section that could be accessed and set with set(String key, Object value, String... comments) and get(String key).

Test converters

All the converters should be unit tested too.
That would prevent problems like the one I fixed in 8555f22

Inner classes throws InstantiationException

Stacktrace:

de.cubeisland.engine.reflect.exception.ReflectedInstantiationException: Failed to create an instance of hu.tryharddood.advancedkits.Kit$KitConfig
        at de.cubeisland.engine.reflect.Reflector.create(Reflector.java:128) ~[?:?]
        at de.cubeisland.engine.reflect.Reflector.load(Reflector.java:70) ~[?:?]
        at de.cubeisland.engine.reflect.Reflector.load(Reflector.java:87) ~[?:?]
        at hu.tryharddood.advancedkits.Kit.<init>(Kit.java:34) ~[?:?]
        at hu.tryharddood.advancedkits.KitManager.loadKits(KitManager.java:53) ~[?:?]
        at hu.tryharddood.advancedkits.AdvancedKits.onEnable(AdvancedKits.java:42) ~[?:?]
        at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:271) ~[spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:337) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:405) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at org.bukkit.craftbukkit.v1_11_R1.CraftServer.enablePlugin(CraftServer.java:376) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at org.bukkit.craftbukkit.v1_11_R1.CraftServer.enablePlugins(CraftServer.java:326) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at net.minecraft.server.v1_11_R1.MinecraftServer.t(MinecraftServer.java:421) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at net.minecraft.server.v1_11_R1.MinecraftServer.l(MinecraftServer.java:382) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at net.minecraft.server.v1_11_R1.MinecraftServer.a(MinecraftServer.java:337) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at net.minecraft.server.v1_11_R1.DedicatedServer.init(DedicatedServer.java:272) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at net.minecraft.server.v1_11_R1.MinecraftServer.run(MinecraftServer.java:544) [spigot-1.11.2.jar:git-Spigot-65a0347-a552117]
        at java.lang.Thread.run(Unknown Source) [?:1.8.0_121]
Caused by: java.lang.InstantiationException: hu.tryharddood.advancedkits.Kit$KitConfig
        at java.lang.Class.newInstance(Unknown Source) ~[?:1.8.0_121]
        at de.cubeisland.engine.reflect.Reflector.create(Reflector.java:118) ~[?:?]
        ... 16 more
Caused by: java.lang.NoSuchMethodException: hu.tryharddood.advancedkits.Kit$KitConfig.<init>()
        at java.lang.Class.getConstructor0(Unknown Source) ~[?:1.8.0_121]
        at java.lang.Class.newInstance(Unknown Source) ~[?:1.8.0_121]
        at de.cubeisland.engine.reflect.Reflector.create(Reflector.java:118) ~[?:?]
        ... 16 more

Code:

public class Kit
{
	private KitConfig config;

	public Kit(String name)
	{
		this.config = instance.factory.load(KitConfig.class, getSaveFile());
		this.config.reload();
		this.config.save();
	}

	public File getSaveFile()
	{
		File file = new File(instance.getDataFolder() + File.separator + "kits", "test" + ".yml");
		if (!instance.getDataFolder().exists()) {
			instance.getDataFolder().mkdir();
		}
		if (!file.exists()) {
			try {
				file.createNewFile();
			}
			catch (IOException e) {
				instance.getLogger().info("Couldn't create the savefile for " + "test");
				instance.getLogger().info(" -- StackTrace --");
				e.printStackTrace();
				instance.getLogger().info(" -- End of StackTrace --");
			}
		}
		return file;
	}

	public void save()
	{
		this.config.save();
	}

	public KitConfig getConfig()
	{
		return this.config;
	}

	public class KitConfig extends ReflectedYaml
	{
		@Name("Flags")
		public Flags flags;

		public class Flags implements Section
		{
			@Comment("Should players see this kit in the gui?")
			public boolean visible        = true;
		}
}

Fields without a default will cause a NPE

When a field doesn't have a default value the node in MapNode.java:96 seems to be null and causes a NPE.

This should be avoided. You should assume save defaults.

Naming conventions

It should be user controllable how field names get transformed to nodes and vice versa

Fields can't be null in Sections

I discovered this problem with a custom converter, but it turns out it's global problem.

In the stack trace below the field 'test' is: 'public String test = null' It's never used in the plugin.

NB: I haven't tested this without sections.

15:42:58 [SEVERE] Error occurred while disabling RegionSpawn v1.0-SNAPSHOT (Is it up to date?)
de.cubeisland.engine.configuration.InvalidConfigurationException: Error while saving Configuration!
    at de.cubeisland.engine.configuration.codec.MultiConfigurationCodec.saveChildConfig(MultiConfigurationCodec.java:74)
    at de.cubeisland.engine.configuration.MultiConfiguration.saveChild(MultiConfiguration.java:63)
    at de.cubeisland.engine.configuration.MultiConfiguration.save(MultiConfiguration.java:162)
    at de.cubeisland.engine.configuration.Configuration.save(Configuration.java:227)
    at de.cubeisland.devs.regionspawn.RegionSpawn.onDisable(RegionSpawn.java:109)
    at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:219)
    at org.bukkit.plugin.java.JavaPluginLoader.disablePlugin(JavaPluginLoader.java:481)
    at org.bukkit.plugin.SimplePluginManager.disablePlugin(SimplePluginManager.java:400)
    at org.bukkit.plugin.SimplePluginManager.disablePlugins(SimplePluginManager.java:393)
    at org.bukkit.craftbukkit.v1_6_R2.CraftServer.disablePlugins(CraftServer.java:277)
    at net.minecraft.server.v1_6_R2.MinecraftServer.stop(MinecraftServer.java:343)
    at net.minecraft.server.v1_6_R2.MinecraftServer.run(MinecraftServer.java:450)
    at net.minecraft.server.v1_6_R2.ThreadServerApplication.run(SourceFile:582)
Caused by: de.cubeisland.engine.configuration.InvalidConfigurationException: Error while converting Section into a MapNode!
Path: test
Section: class de.cubeisland.devs.regionspawn.config.InternalsConfig
Field: test
    at de.cubeisland.engine.configuration.InvalidConfigurationException.of(InvalidConfigurationException.java:53)
    at de.cubeisland.engine.configuration.codec.ConfigurationCodec.convertSection(ConfigurationCodec.java:387)
    at de.cubeisland.engine.configuration.codec.MultiConfigurationCodec.convertSection(MultiConfigurationCodec.java:262)
    at de.cubeisland.engine.configuration.codec.MultiConfigurationCodec.saveChildConfig(MultiConfigurationCodec.java:70)
    ... 12 more
Caused by: java.lang.NullPointerException
    at de.cubeisland.engine.configuration.node.MapNode.setExactNode(MapNode.java:103)
    at de.cubeisland.engine.configuration.node.ParentNode.setNodeAt(ParentNode.java:44)
    at de.cubeisland.engine.configuration.codec.ConfigurationCodec.convertSection(ConfigurationCodec.java:383)
    ... 14 more

Converter Annotation

add a converter annotation to use a specific converter for a field instead of the default

Mode which changed wrong config values

At the moment the ConfigurationAPI is thrown an exception if a value couldn't be parsed and this could destroy the whole program.

I would like to enter a mode where the wrong config values will be repaired.

So maybe a strict mode were a exception is thrown and an other mode where wrong values will be fixed.

Maybe implement it like this:

/**
 * This method is called if the fromNode-method throws the ConversionException
 * and the api is not in strict mode.
 *
 * @param node the node to deserialize
 * @param preset the preset value
 *
 * @return the deserialized field value
 */
public T getDefault(Node node, T preset)

// Or the fromNode method get's the preset param

(It's a method for the Converter interface)

default enum converter

I would like to have a default enum converter.
A converter which should be invoked after no registered converter, fitting the requirements, was found and before the section converter will be used.

It should make it possible to get an enum field of the enum even if the case isn't the same.
So lowercased values should also get the right value even if the constants are all upper cased.

InvalidConfigurationException by starting the mc-server

An InvalidConfigurationException is thrown by starting the mc-server if it has to create the configuration files.

11:46:15 [INFORMATION] [CubeEngine] Loading CubeEngine v1.0.0-SNAPSHOT
11:46:16 [INFORMATION] [CubeEngine] Clearing the temporary folder '/home/boeserwolf91/Schreibtisch/Minecraft/Server/plugins/CubeEngine/temp'...
11:46:16 [INFORMATION] [CubeEngine] Temporary folder cleared!
11:46:16 [SCHWERWIEGEND] Error while saving Configuration! initializing CubeEngine v1.0.0-SNAPSHOT (Is it up to date?)
de.cubeisland.engine.configuration.InvalidConfigurationException: Error while saving Configuration!
        at de.cubeisland.engine.configuration.codec.ConfigurationCodec.save(ConfigurationCodec.java:84)
        at de.cubeisland.engine.configuration.Configuration.save(Configuration.java:126)
        at de.cubeisland.engine.configuration.MultiConfiguration.save(MultiConfiguration.java:166)
        at de.cubeisland.engine.configuration.Configuration.save(Configuration.java:227)
        at de.cubeisland.engine.configuration.Configuration.reload(Configuration.java:190)
        at de.cubeisland.engine.configuration.MultiConfiguration.reload(MultiConfiguration.java:153)
        at de.cubeisland.engine.configuration.Configuration.load(Configuration.java:332)
        at de.cubeisland.engine.configuration.Configuration.load(Configuration.java:346)
        at de.cubeisland.engine.core.bukkit.BukkitCore.onLoad(BukkitCore.java:255)
        at org.bukkit.craftbukkit.v1_6_R3.CraftServer.loadPlugins(CraftServer.java:246)
        at org.bukkit.craftbukkit.v1_6_R3.CraftServer.<init>(CraftServer.java:219)
        at net.minecraft.server.v1_6_R3.PlayerList.<init>(PlayerList.java:56)
        at net.minecraft.server.v1_6_R3.DedicatedPlayerList.<init>(SourceFile:11)
        at net.minecraft.server.v1_6_R3.DedicatedServer.init(DedicatedServer.java:107)
        at net.minecraft.server.v1_6_R3.MinecraftServer.run(MinecraftServer.java:393)
        at net.minecraft.server.v1_6_R3.ThreadServerApplication.run(SourceFile:583)
Caused by: de.cubeisland.engine.configuration.InvalidConfigurationException: Error while converting Section into a MapNode!
Path: commands
Section: class de.cubeisland.engine.core.bukkit.BukkitCoreConfiguration
Field: commands
        at de.cubeisland.engine.configuration.InvalidConfigurationException.of(InvalidConfigurationException.java:53)
        at de.cubeisland.engine.configuration.codec.ConfigurationCodec.convertSection(ConfigurationCodec.java:387)
        at de.cubeisland.engine.configuration.codec.ConfigurationCodec.save(ConfigurationCodec.java:80)
        ... 15 more
Caused by: java.lang.NoSuchMethodException: de.cubeisland.engine.core.CoreConfiguration$CommandSection.<init>(de.cubeisland.engine.core.bukkit.BukkitCoreConfiguration)
        at java.lang.Class.getConstructor0(Class.java:2810)
        at java.lang.Class.getDeclaredConstructor(Class.java:2053)
        at de.cubeisland.engine.configuration.codec.ConfigurationCodec.newSectionInstance(ConfigurationCodec.java:259)
        at de.cubeisland.engine.configuration.codec.ConfigurationCodec.convertField(ConfigurationCodec.java:414)
        at de.cubeisland.engine.configuration.codec.ConfigurationCodec.convertSection(ConfigurationCodec.java:383)
        ... 16 more
11:46:16 [INFORMATION] Preparing level "world"
11:46:16 [INFORMATION] Preparing start region for level 0 (Seed: 8083085911403211657)
11:46:17 [INFORMATION] Preparing spawn area: 29%
11:46:18 [INFORMATION] Preparing start region for level 1 (Seed: 8083085911403211657)
11:46:19 [INFORMATION] Preparing start region for level 2 (Seed: 8083085911403211657)
11:46:19 [INFORMATION] [CubeEngine] Enabling CubeEngine v1.0.0-SNAPSHOT
11:46:19 [INFORMATION] [CubeEngine] Disabling CubeEngine v1.0.0-SNAPSHOT
11:46:19 [DEBUG] [CubeEngine] utils cleanup
11:46:19 [DEBUG] [CubeEngine] file manager cleanup
11:46:19 [INFORMATION] [CubeEngine] Clearing the temporary folder '/home/boeserwolf91/Schreibtisch/Minecraft/Server/plugins/CubeEngine/temp'...
11:46:19 [SCHWERWIEGEND] Error occurred while disabling CubeEngine v1.0.0-SNAPSHOT (Is it up to date?)
java.lang.NullPointerException
        at de.cubeisland.engine.core.bukkit.BukkitFileManager.cycleLogs(BukkitFileManager.java:52)
        at de.cubeisland.engine.core.bukkit.BukkitCore.onDisable(BukkitCore.java:517)
        at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:219)
        at org.bukkit.plugin.java.JavaPluginLoader.disablePlugin(JavaPluginLoader.java:481)
        at org.bukkit.plugin.SimplePluginManager.disablePlugin(SimplePluginManager.java:400)
        at de.cubeisland.engine.core.bukkit.BukkitCore.onEnable(BukkitCore.java:383)
        at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:217)
        at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:457)
        at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:381)
        at org.bukkit.craftbukkit.v1_6_R3.CraftServer.loadPlugin(CraftServer.java:284)
        at org.bukkit.craftbukkit.v1_6_R3.CraftServer.enablePlugins(CraftServer.java:266)
        at net.minecraft.server.v1_6_R3.MinecraftServer.l(MinecraftServer.java:315)
        at net.minecraft.server.v1_6_R3.MinecraftServer.f(MinecraftServer.java:292)
        at net.minecraft.server.v1_6_R3.MinecraftServer.a(MinecraftServer.java:252)
        at net.minecraft.server.v1_6_R3.DedicatedServer.init(DedicatedServer.java:152)
        at net.minecraft.server.v1_6_R3.MinecraftServer.run(MinecraftServer.java:393)
        at net.minecraft.server.v1_6_R3.ThreadServerApplication.run(SourceFile:583)
11:46:19 [INFORMATION] Server permissions file permissions.yml is empty, ignoring it
11:46:19 [INFORMATION] Done (3,380s)! For help, type "help" or "?"

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.