Giter Club home page Giter Club logo

horned-owl's Introduction

Horned OWL

Crates.io docs.rs

Horned-OWL is a library for manipulating data written using the Web Ontology Language (OWL). While there are a number of libraries that manipulate this form of data such as the OWL API, they can be quite slow. Horned-OWL is aimed at allowing ontologies with millions of terms.

The library now implements all of OWL2, and we are working on further parser functionality. We are testing it with real world tasks, such as parsing the Gene Ontology, which is does in 2-3 seconds which is 20-40 times faster than the OWL API.

Library

To use the latest version of the library in your Rust project, add the following line to your Cargo.toml file:

[dependencies]
...
horned-owl = "1.0.0"

Command Line Tools

A set of command line tools are available as in Horned Bin.

horned-owl's People

Contributors

althonos avatar eugenio2192 avatar filippodebortoli avatar jannahastings avatar jaydchan avatar konradhoeffner avatar phillord avatar phillord-ncl 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

horned-owl's Issues

Reexport `curie` crate or `PrefixMapping` to help versioning

Currently horned-owl needs the user to add the curie dependency manually with the exact same version as the one used by horned-owl, because they need PrefixMapping available if they use a parser or a serializer. Because updating the internal curie version can break the code relying on it, any curie update is to be treated as a breaking change, which is extremely cumbersome.

Improving IRI handling using `oxiri`

At the moment, horned comes with its own IRI data structures IRI and IRIString, plus related traits ForIRI and WithIRI, methods for IRI resolution, and so on.

I found out that the @oxigraph project provides the oxiri crate, which comes with most of the features that horned seems to need.

@phillord do you think horned could benefit from using oxiri for handling IRIs or would you stick with the current implementation?

SubObjectPropertyOf has inversed field types

Current signature of SubObjectPropertyOf is:

pub struct SubObjectPropertyOf {
    pub super_property: SubObjectPropertyExpression,
    pub sub_property: ObjectProperty,
}

where it should be

pub struct SubObjectPropertyOf {
    pub super_property: ObjectProperty,
    pub sub_property: SubObjectPropertyExpression,
}

DataUnionOf handling is broken

The file data-union.owx and equivalents does not contain a union but an intersection (which is tested in another file!).

Fixing this suggests that both OWX and RDF parsers appear to be broken and cannot handle this construct.

Use of tuple/struct style variants is inconsistent

The rules for when to use a tuple, when to use a struct should be made explicit and enforced evenly. I think ObjectIntersectionOf and ObjectUnionOf should be tuples (since they have only one entity inside).

Also the naming is inconsistent. In most cases, these are short (from, to), acronym style (ope). But then we have annotation_subject and `annotation.

This issue was first raised in #18

Subclass axioms are serialized/deserialized in the wrong order

The OWL XML2 specification states that in SubClassOf axioms the subclass appears before the superclass:

  <xsd:complexType name="SubClassOf">
    <xsd:complexContent>
      <xsd:extension base="owl:ClassAxiom">
        <xsd:sequence>
          <xsd:group ref="owl:ClassExpression"/>
          <!-- This is the subexpression -->
          <xsd:group ref="owl:ClassExpression"/>
          <!-- This is the superexpression -->
        </xsd:sequence>
      </xsd:extension>
    </xsd:complexContent>
  </xsd:complexType>
  <xsd:element name="SubClassOf" type="owl:SubClassOf"/>

The current XML parser however will deserialize it in the opposite order:

        b"SubClassOf" => {
            SubClassOf {
                super_class:from_start(r, e)?,
                sub_class:from_next(r)?,
            }.into()
        }

And the current XML serializer will also do it the wrong way:

contents!{
    SubClassOf, self,
    (&self.super_class,
     &self.sub_class)
}

The same bug also affects SubObjectPropertyOf and SubDataPropertyOf.

Refactoring `vocab` module and fix potentially broken methods.

I opened the broken-tests-vocabulary branch on my fork. I introduced a few tests in the vocab module that are currently failing:

  • vocab::test_is_xsd_datatype
  • vocab::test_namespace_in_entity_for_iri
  • vocab::test_to_built_in_entity

In addition, I left a few additional TODOs throughout the module:

  • evaluate if vocab::WithIRI could be moved to model or potentially into a new iri module together with model::IRI and model::ForIRI
  • evaluate if vocab::IRIString can be deprecated and replaced by model::IRI
  • fix vocab::to_built_in_entity to make the corresponding test pass
  • fix vocab::entity_for_iri to make the corresponding test pass
  • fix vocab::is_xsd_datatype to make the corresponding test pass

Already present TODOs:

  • implement Meta::all() for vocab::Vocab

I consider these to be potential bugs (hence the labels).

Type of cardinality fields

Just a quick thought: given the cardinalities are unsigned, shouldn't ClassExpression::ObjectMinCardinality.n (and the like) be declared as u32 instead of i32 ?

At least two class expressions not enforced

There are a number of places where the spec requires at least two class expressions (for example, DisjointUnion but horned allows any number.

The only solution I can think of is to use Class, Class, Vec<Class> as a compound type. This is fairly long winded. It could be made nicer with an iterator or a wrapper Vec2+ object.

a3b7084

Support testing output against the OWL API

We currently test whether we can read the OWL API output, as all the test files from bubo are producing using it.

But there is nothing that will test whether the OWL API can read horned-owl files.

We can achieve this by producing something by converting form the XML file into various outputs. Then write a small tool in bubo to produce output easily comparable to horned-summary,

Parser only extracts annotations before a class declaration

Hi!

When presented with the following OWL XML file horned_owl::reader will extract all the annotations for the declared class as expected:

<Ontology xmlns="http://www.w3.org/2002/07/owl#">
  <Declaration>
      <Annotation>
          <AnnotationProperty IRI="oboInOwl:id"/>
          <Literal datatypeIRI="xsd:string">TST:001</Literal>
      </Annotation>
      <Class IRI="http://purl.obolibrary.org/obo/TST_001"/>
  </Declaration>
</Ontology>

However, when given the following file, where XML elements in the declaration have been switched, the extracted DeclareClass struct has an empty annotation set:

<Ontology xmlns="http://www.w3.org/2002/07/owl#">
  <Declaration>
      <Class IRI="http://purl.obolibrary.org/obo/TST_001"/>
      <Annotation>
          <AnnotationProperty IRI="oboInOwl:id"/>
          <Literal datatypeIRI="xsd:string">TST:001</Literal>
      </Annotation>
  </Declaration>
</Ontology>

I found nothing in the OWL 2 XML serialization spec about ordering of XML terms, so I have the feeling this is a bug with the reader.

Refactor the crate into a Cargo workspace.

First mentioned here. I open an issue to make sure this is not forgotten.

The idea is to split the horned-owl library from the horned tool suite, obtaining a library crate and a binary crate as a result.

One idea could be to use Cargo workspaces and reorganize the repository.

HasKey should take a vector of PropertyExpression

Coming from the OWL 2 new features document:

An HasKey axiom states that each named instance of a class is uniquely identified by a (data or object) property or a set of properties - that is, if two named instances of the class coincide on values for each of key properties, then these two individuals are the same.

The functional syntax for HasKey is:

HasKey := 'HasKey' '(' axiomAnnotations ClassExpression '(' { ObjectPropertyExpression } ')' '(' { DataPropertyExpression } ')' ')'

so you can just group all expressions together in the same Vec<PropertyExpression>:

pub struct HasKey {
    pub ce: ClassExpression,
    pub pe: Vec<PropertyExpression>,
}

Check cardinality in RDF reader.

This matcher (reader.rs:1200)

            [[_, Term::OWL(VOWL::Cardinality), literal],//:
             [_, Term::OWL(VOWL::OnProperty), pr],//:
             [_, Term::RDF(VRDF::Type), Term::OWL(VOWL::Restriction)]

incorrectly always returns ObjectExactCardinality when it should check the property type.

This is fixed in the parametric testing branch, but the other matchers need checking for the same thing.

Parsing RDF/XML nested declared entities

Hi,
a few days ago I tested horned-summary to see how it would work.
Out of chance, I chose an ontology that gave horned quite the panicking headache.
This is the Basic Formal Ontology (BFO) and, after enough fiddling, I found out that the issue lied within rio (issue) and the way it parsed declared entities. The fix has then been integrated in version 0.8.0 of rio (release).
All happy, I went ahead and tried to bump the dependency on horned and found out (after additional fiddling) that pretty_rdf would need to be updated as well (PR here).
Once that PR gets accepted, I would be happy to open a PR here with the dependency bump.

Missing Tests for written files against OWL API

Currently we do not test rendered files against the OWL API.

A simple option would be just to load them using bubo and see that it does not crash. Something more complex would be to use bubo to produce output in the same format as horned-summary and diff the two.

Release 0.11

I plan to release 0.11 based on the current devel in the next few days. Any comments or last changes would please let me know!

I am going to finally bite the bullet and run cargo fmt over the whole thing. Might as well accept a bog log of cosmetic diffs all at once, but I thought to leave that to just before the release.

Support for annotations in `AnnotationAssertion` axiom

Hi !

Is there a way with the current API to generate the following OWL axiom:

AnnotationAssertion(Annotation(oboInOwl:hasDbXref "IEDB:RV") obo:IAO_0000115 obo:DOID_0040070 "something")

? It does not seem that AnnotationAssertion has any support for additional annotations (where OWLAnnotationAssertionAxiomImpl in the Java OWLAPI does).

Broken test files still pass

I have modified "class.owx" to contain this obvious breakage.

   <Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
    
    I am broken


    <Declaration>
        <Class IRI="#C"/>
    </Declaration>

But, obvious as it is, the tests all seem to pass and I do not know why.

Check documentation

Doctests for ComponentMappedIndex actually refer to ComponentMappedOntology

`horned_owl::model::Ontology.mut_doc_iri` does not return a mutable reference

In the current trait definition for Ontology, there's no way to get a mutable reference to the doc IRI:

pub trait Ontology {
    fn id(&self) -> &OntologyID;
    fn mut_id(&mut self) -> &mut OntologyID;
    fn doc_iri(&self) -> &Option<IRI>;
    fn mut_doc_iri(&mut self) -> &Option<IRI>;
}

mut_doc_iri signature should be changed to return a mutable reference.

anonymous subclass not parsed in RDF/XML

The attached files contain an axiom (bearer_of some mononucleate) SubClassOf (has_part some nucleus).

If I load the OWL/XML file, this axiom is found in the loaded ontology, but if I load the RDF/XML file, it is not. I'm using horned-owl 0.11.0, using:

horned_owl::command::parse_path(Path::new("ontology.owx"))

ontologies.zip

ObjectPropertyChain variant has the wrong type

Current type of SubObjectPropertyExpression::ObjectPropertyChain is:

enum SubObjectPropertyExpression {
    ObjectPropertyChain(Vec<ObjectProperty>)
    ObjectPropertyExpression(ObjectPropertyExpression)
}

or the functional grammar production rule for this node is:

SubObjectPropertyOf := 'SubObjectPropertyOf' '(' axiomAnnotations subObjectPropertyExpression superObjectPropertyExpression ')'
subObjectPropertyExpression := ObjectPropertyExpression | propertyExpressionChain
propertyExpressionChain := 'ObjectPropertyChain' '(' ObjectPropertyExpression ObjectPropertyExpression { ObjectPropertyExpression } ')'
superObjectPropertyExpression := ObjectPropertyExpression

Hence, the right signature in Rust should be

ObjectPropertyChain(Vec<ObjectPropertyExpression>)

(to allow inverse properties in property chains).

Reader gives unhelpful messages

let aa = AnnotatedAxiom::from_start(&mut r, e)?;
ont.insert(aa);

This method call in read can return several kinds of error message, not all of which report position information.

Trait bounds should be tightened

This elaborates on #74.

The library currently contains two traits, indexed::ForIndex and model::ForIRI, whose job is to collect trait bounds for types that can be used to build ontology indexes and IRIs.

Currently, many methods require e.g. A: ForIRI where it is not strictly necessary.
For instance, model::Build::new() should work without any bound on A, going from the current implementation to

pub struct Build<A>(
    RefCell<BTreeSet<IRI<A>>>,
    RefCell<BTreeSet<AnonymousIndividual<A>>>,
);

impl<A> Build<A> {
    pub fn new() -> Build<A> {
        Build(RefCell::new(BTreeSet::new()), RefCell::new(BTreeSet::new()))
    }
}

Another example is the implementation of Clone for indexed::OneIndexedOntology which currently is

impl<A: ForIRI, AA: ForIndex<A>, I: Clone> Clone for OneIndexedOntology<A, AA, I> {
    fn clone(&self) -> Self {
        OneIndexedOntology(
            self.0.clone(),
            self.1.clone(),
            Default::default(),
        )
    }
}

but may be simplified to

impl<A: Clone, AA, I: Clone> Clone for OneIndexedOntology<A, AA, I> {
    fn clone(&self) -> Self {
        OneIndexedOntology(self.0.clone(), self.1.clone(), Default::default())
    }
}

so that AA can be fully generic (which makes sense given that we use PhantomData<AA> there).

The goal is to remove any reference to ForIRI and ForIndex where possible, and potentially question whether these traits are necessary at all.

UnexpectedEOF when parsing example from Wikipedia page.

This snippet of code:

let mut bytes = ontology.as_bytes();
let reader = horned_owl::io::owx::reader::read(&mut bytes).unwrap();

panics if I use the following example (taken from the OWL Wikipedia page):

let ontology = 
    r#"<Ontology ontologyIRI="http://example.org/tea.owl" ...>
    <Prefix name="owl" IRI="http://www.w3.org/2002/07/owl#"/>
    <Declaration>
        <Class IRI="Tea"/>
    </Declaration>
    </Ontology>"#;

and returns successfully if I add the xmlns attribute to the ontology:

let ontology = 
    r#"<Ontology xmlns="http://www.w3.org/2002/07/owl#" ontologyIRI="http://example.org/tea.owl" ...>
    <Prefix name="owl" IRI="http://www.w3.org/2002/07/owl#"/>
    <Declaration>
        <Class IRI="Tea"/>
    </Declaration>
    </Ontology>"#;

Is it intended behavior? I would expect the first ontology to be as well-formed as the second one.

Intended way of removing components

Sorry, I think this is more of an advice request than an issue, do you have some discussion board for this kind of questions?

For context: I am trying to remodel a DL ontology into RL. And I found no proper way of doing it with Robot so I opted to do it with this API. The general idea is to use robot to validate the profile and use the report to build a script with some heuristic on what to do in each case. ( If there is some better approach please enlighten me).

Now with that clarified, I was wondering what is the "proper" way of modifying an existing ontology. Should I modify the working Mutable ontology or is it cleaner to just create a parallel ontology that is being filled according to some rules. If the ontology is to be mutated, how can I borrow the RcComponentMappedOntology in a way that it can be mutated iteratively? ( I tried making it mutable but if i call "into_iter" or "i" borrows the ontology so I can't change it which makes sense, probably I'm missing something on how to use the indexed ontology.

Here is my code, and the idea is to implement the heuristic in each of the match cases:

use horned_owl::io::{owx::reader, ParserConfiguration};
use horned_owl::model::*;
use horned_owl::ontology::component_mapped::RcComponentMappedOntology;
use std::fs::File;
use std::io::BufReader;

fn main() {
    let ontology_path: &str = "ontology.owx";
    let file = File::open(&ontology_path).unwrap();
    let mut bufreader = BufReader::new(file);
    let (ont, _) = reader::read(&mut bufreader, ParserConfiguration::default()).unwrap();

    let amo: RcComponentMappedOntology = ont.into();

    for c in amo.into_iter() {
        match &c.component {
            Component::TransitiveObjectProperty(top) => (),
            Component::InverseFunctionalObjectProperty(ifop) => (),
            Component::SubClassOf(SubClassOf { sup, sub }) => {
                match sup {
                    ClassExpression::ObjectAllValuesFrom{ope: a, bce: b} => (),
                    _ => ()
                };
            },
            Component::ObjectPropertyDomain(ObjectPropertyDomain{ope: op, ce: cc}) => {
                match cc {
                    ClassExpression::ObjectUnionOf(vc) => println!("{:?}", vc),
                    _ => ()
                };
            },
            _ => (),
        }
    }
}

Range is optional in cardinality expressions

Grammar allow optional DataRange in cardinality expressions:

ObjectMinCardinality := 'ObjectMinCardinality' '(' nonNegativeInteger ObjectPropertyExpression [ ClassExpression ] ')'
ObjectMaxCardinality := 'ObjectMaxCardinality' '(' nonNegativeInteger ObjectPropertyExpression [ ClassExpression ] ')'
ObjectExactCardinality := 'ObjectExactCardinality' '(' nonNegativeInteger ObjectPropertyExpression [ ClassExpression ] ')'

DataMinCardinality := 'DataMinCardinality' '(' nonNegativeInteger DataPropertyExpression [ DataRange ] ')'
DataMaxCardinality := 'DataMaxCardinality' '(' nonNegativeInteger DataPropertyExpression [ DataRange ] ')'
DataExactCardinality := 'DataExactCardinality' '(' nonNegativeInteger DataPropertyExpression [ DataRange ] ')'

so the code should be

DataMinCardinality {
    n: i32,
    dp: DataProperty,
    dr: Option<DataRange>
}

Missing axioms in current model

The following annotation property axioms are missing from the model:

  • AnnotationPropertyDomain
  • AnnotationPropertyRange

The following class axioms is missing from the model:

  • DisjointUnion

UnexpectedEof when reading certain OWLXML

This version of obi.owl is accepted by ROBOT with warnings but horned-owl rejects it:

$ curl -LO http://purl.obolibrary.org/obo/obi/2021-04-06/obi.owl
$ robot convert -i obi.owl -o obi.owx
2021-06-07 20:54:03,041 ERROR org.obolibrary.robot.IOHelper - Input ontology contains 3 triple(s) that could not be parsed:
 - <http://purl.obolibrary.org/obo/OBI_0000979> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> _:genid2147501962.
 - <http://purl.obolibrary.org/obo/OBI_0000998> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> _:genid2147501963.
 -  <http://purl.obolibrary.org/obo/OBI_0000958> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> _:genid2147501961.

$ horned-parse obi.owx
Error: UnexpectedEof { pos: 12817470 }

This is the offending OWLXML from near the end of obi.owx, and when it's removed horned-owl is happy with file:

    <AnnotationAssertion>
        <AnnotationProperty IRI="http://purl.obolibrary.org/obo/IAO_0000412"/>
        <AnonymousIndividual nodeID="_:genid2147502004"/>
        <IRI>http://purl.obolibrary.org/obo/uberon.owl</IRI>
    </AnnotationAssertion>
    <AnnotationAssertion>
        <AnnotationProperty abbreviatedIRI="owl:minQualifiedCardinality"/>
        <AnonymousIndividual nodeID="_:genid2147502005"/>
        <Literal datatypeIRI="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">2</Literal>
    </AnnotationAssertion>

I'm not 100% sure that the ROBOT warnings about unparsed triples match up with this OWLXML. I think I don't mind if horned-owl rejects this, but we've found other similar cases that horned-owl won't read, and the UnexpectedEof messages seems misleading.

DataSomeValuesFrom and DataAllValuesFrom should support data property chaining

Once again, the grammar show support for

DataSomeValuesFrom := 'DataSomeValuesFrom' '(' DataPropertyExpression { DataPropertyExpression } DataRange ')'

DataAllValuesFrom := 'DataAllValuesFrom' '(' DataPropertyExpression { DataPropertyExpression } DataRange ')'

So the enum variants should store Vec<DataProperty> instead of DataProperty.

ObjectHasSelf should allow ObjectPropertyExpression

ObjectHasSelf is defined in the grammar:

ObjectHasSelf := 'ObjectHasSelf' '(' ObjectPropertyExpression ')'

but in the code it is only ObjectHasSelf(ObjectProperty).

(Also, it feels weird that this one is the only other tuple variant with Class while all the other are structs variants, it's not that elegant to mix both styles IMO).

Consider 1.0 release

There has been a fair bit of development on the devel branch and it needs merging to main as a new release.

I think this should be the 1.0 release, with the SWRL rules support that I am working on as the current blocker for this.

Thoughts welcome.

RFC: Significant Model change

I am trialing out a significant change to the data model for the next release. The main idea is to move IRI<Rc<str>> to being IRI<Borrow<str>>.

This addresses a long standing problem; horned is currently only usuable in one thread, because of the Rc. This change means that IRI can wrap Rc but also Arc or even 'str.

The big disadvantage is that it means that I have to make all most every method generic. This is turning out to be a significant PITA because it breaks lots of the macros taht I have used both in model and in the IO libraries. Model I have managed to fix. The IO libraries, I think, are too hard so I will either have to unwind all the macros or turn them into proceedural ones. Either way, it's a big change and I'd like to get it right.

I am still debating whether this should be IRI<Borrow<str>> or the weaker IRI<AsRef<str>>. I think that the core model does not require Borrow<str>, but any form of caching does. But the tighter restriction could be added where needed.

Currently, Build also includes an Rc which means it cannot be used cross thread. Although I put the Rc into make build easy to clone and share, I actually seem to have forgotten about it, because all the IO libraries use &Build rather than just cloning it. So, rather than make this generic, I have simply removed the Rc although maintained the interior mutability. I think this makes sense. Making Build generic over Arc would be a pain and the multi-threaded the cost of co-ordination would probably outweigh the benefit of caching.

As I don't have a working version yet (because of all the IO changes) I've added an outline version of IRI and Build here.

https://github.com/phillord/horned-owl/tree/scratch/borrow-model

@althonos @jannahastings Thoughts welcome

horned with no options panics

It should print the help out

~/src/rust/horned-owl/main/horned-bin$ horned
thread 'main' panicked at 'called Option::unwrap() on a None value', horned-bin/src/bin/horned.rs:38:11
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace

Strange delay on write to stdout

I tried running the tools in src/bin/ under main and devel, and I'm getting a weird delay when I pipe to STDOUT. This delay gets larger as the source file gets larger. The same (or similar) delay when we write with py-horned-owl. Here's a bash session (Ubuntu 18.04 inside Docker under macOS... sorry):

$ curl -LO http://purl.obolibrary.org/obo/obi/obi_core.owl
$ robot convert -i obi_core.owl -o obi_core.owx 
$ time horned-parse obi_core.owx
Parse Complete: "obi_core.owx"

real    0m0.015s
user    0m0.005s
sys     0m0.006s
$ time horned-round obi_core.owx
# skipping file contents
real    0m0.074s
user    0m0.021s
sys     0m0.051s
$ time horned-round obi_core.owx > obi_core_2.owx

real    0m9.618s
user    0m0.070s
sys     0m0.640s

The last command is the problem. The real time is way longer than the user and sys time.

My naive guess would be that something is waiting to flush...

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.