aisamanra / config-ini Goto Github PK
View Code? Open in Web Editor NEWA monadic DSL for simple configuration parsing
License: BSD 3-Clause "New" or "Revised" License
A monadic DSL for simple configuration parsing
License: BSD 3-Clause "New" or "Revised" License
Hi!
It's unclear to me how to handle a single space as a string. If I provide it as a default via a default config, like { _bla = " " }
, then this will end up in the .ini as bla =
(2 spaces after =
).
This then gets read back into the datastructure as the empty string.
How can I handle this? Quoting doesn't work, since that just includes the quotes itself in the string.
Build fails in conjunction with ghc-9.6 and latest stackage nightly:
/var/stackage/work/unpack-dir/unpacked/config-ini-0.2.5.0-a2138799480e166f8d7ee42bdfa2812de20508451c5bc39d9228443bec79c879/test/ini-compat/Main.hs:85:30: error: [GHC-39999]
• Ambiguous type variable ‘f0’ arising from a use of ‘Gen.element’
prevents the constraint ‘(Foldable f0)’ from being solved.
Probable fix: use a type annotation to specify what ‘f0’ should be.
Potentially matching instances:
instance Foldable (Either a) -- Defined in ‘Data.Foldable’
instance Foldable Seq.Seq -- Defined in ‘Data.Sequence.Internal’
...plus 7 others
...plus 47 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the second argument of ‘(<$>)’, namely ‘Gen.element ";#"’
In the first argument of ‘(<*>)’, namely
‘I2.CommentLine <$> Gen.element ";#"’
In the expression:
I2.CommentLine <$> Gen.element ";#" <*> textChunk
|
85 | I2.CommentLine <$> Gen.element ";#" <*> textChunk
The latest release of config-ini has a constraint containers ==0.5.*
that prevents us from building the package with ghc-8.6.1. When that constraint is lifted, however, the build succeeds just fine. Could you please lift that constraint on Hackage?
Observed behavior: use of emitIniFile
results in output that has two newlines per line.
Expected behavior: one newline per line.
> emitIniFile defaultConfig parseConfig
"[section]\n\nfoo=1\n\nbar=2\n\n..."
I haven't tracked it down yet, but sometimes when the bidirectional support regenerates a config, newlines are introduced between the section name and the first setting in a section, e.g.,
[section]
setting = foo
[section2]
otherSetting = bar
It looks like input files are required to terminate with a newline (see matterhorn-chat/matterhorn#540) but it would be great if the terminating newline was optional.
I'd like to have file path as the value of a config parameter, but load the referenced file during parsing the configuration. The implementation would look similar to how the other common monads are actually type aliases, e.g Reader a
is actually ReaderT a Identity
etc
You're using ghc flags that are supported only starting with GHC 8.0, and yet your package declares to be compatible with base versions prior to base-4.9:
Configuring component lib from config-ini-0.1.0.0
Preprocessing library config-ini-0.1.0.0...
src/Data/Ini/Config.hs:4:16:
unknown flag in {-# OPTIONS_GHC #-} pragma: -fno-warn-redundant-constraints
I suggest guarding the option with e.g. #if __GLASGOW_HASKELL__ >= 800
if you want to support versions prior to GHC 8.0
Running 3 test suites...
Test suite test-doctest: RUNNING...
src/Data/Ini/Config/Raw.hs:167:8: error:
• Ambiguous type variable ‘f1’ arising from a use of ‘oneOf’
prevents the constraint ‘(Foldable f1)’ from being solved.
Probable fix: use a type annotation to specify what ‘f1’ should be.
These potential instances exist:
instance Foldable (Either a) -- Defined in ‘Data.Foldable’
instance Foldable Seq -- Defined in ‘Data.Sequence.Internal’
instance Foldable Seq.ViewL -- Defined in ‘Data.Sequence.Internal’
...plus five others
...plus 33 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In a stmt of a 'do' block: c <- oneOf ";#"
In the expression:
do c <- oneOf ";#"
txt <- T.pack `fmap` manyTill anySingle eol
return (CommentLine c txt)
In an equation for ‘sComment’:
sComment
= do c <- oneOf ";#"
txt <- T.pack `fmap` manyTill anySingle eol
return (CommentLine c txt)
src/Data/Ini/Config/Raw.hs:167:14: error:
• Ambiguous type variable ‘f1’ arising from the literal ‘";#"’
prevents the constraint ‘(Data.String.IsString
(f1 Char))’ from being solved.
Probable fix: use a type annotation to specify what ‘f1’ should be.
These potential instances exist:
instance (a ~ Char) => Data.String.IsString (Seq a)
-- Defined in ‘Data.Sequence.Internal’
instance [safe] (a ~ Tokens s, Data.String.IsString a, Eq a,
Stream s, Ord e) =>
Data.String.IsString (ParsecT e s m a)
-- Defined in ‘Text.Megaparsec.Internal’
instance (a ~ Char) => Data.String.IsString [a]
-- Defined in ‘Data.String’
...plus two instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘oneOf’, namely ‘";#"’
In a stmt of a 'do' block: c <- oneOf ";#"
In the expression:
do c <- oneOf ";#"
txt <- T.pack `fmap` manyTill anySingle eol
return (CommentLine c txt)
src/Data/Ini/Config/Raw.hs:179:31: error:
• Ambiguous type variable ‘f3’ arising from a use of ‘noneOf’
prevents the constraint ‘(Foldable f3)’ from being solved.
Probable fix: use a type annotation to specify what ‘f3’ should be.
These potential instances exist:
instance Foldable (Either a) -- Defined in ‘Data.Foldable’
instance Foldable Seq -- Defined in ‘Data.Sequence.Internal’
instance Foldable Seq.ViewL -- Defined in ‘Data.Sequence.Internal’
...plus five others
...plus 33 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘some’, namely ‘(noneOf "[]")’
In the second argument of ‘fmap’, namely ‘some (noneOf "[]")’
In a stmt of a 'do' block: name <- T.pack `fmap` some (noneOf "[]")
src/Data/Ini/Config/Raw.hs:179:38: error:
• Ambiguous type variable ‘f3’ arising from the literal ‘"[]"’
prevents the constraint ‘(Data.String.IsString
(f3 Char))’ from being solved.
Probable fix: use a type annotation to specify what ‘f3’ should be.
These potential instances exist:
instance (a ~ Char) => Data.String.IsString (Seq a)
-- Defined in ‘Data.Sequence.Internal’
instance [safe] (a ~ Tokens s, Data.String.IsString a, Eq a,
Stream s, Ord e) =>
Data.String.IsString (ParsecT e s m a)
-- Defined in ‘Text.Megaparsec.Internal’
instance (a ~ Char) => Data.String.IsString [a]
-- Defined in ‘Data.String’
...plus two instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘noneOf’, namely ‘"[]"’
In the first argument of ‘some’, namely ‘(noneOf "[]")’
In the second argument of ‘fmap’, namely ‘some (noneOf "[]")’
src/Data/Ini/Config/Raw.hs:214:30: error:
• Ambiguous type variable ‘f0’ arising from a use of ‘noneOf’
prevents the constraint ‘(Foldable f0)’ from being solved.
Probable fix: use a type annotation to specify what ‘f0’ should be.
These potential instances exist:
instance Foldable (Either a) -- Defined in ‘Data.Foldable’
instance Foldable Seq -- Defined in ‘Data.Sequence.Internal’
instance Foldable Seq.ViewL -- Defined in ‘Data.Sequence.Internal’
...plus five others
...plus 33 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘some’, namely ‘(noneOf "[]=:")’
In the second argument of ‘fmap’, namely ‘some (noneOf "[]=:")’
In a stmt of a 'do' block:
key <- T.pack `fmap` some (noneOf "[]=:")
src/Data/Ini/Config/Raw.hs:214:37: error:
• Ambiguous type variable ‘f0’ arising from the literal ‘"[]=:"’
prevents the constraint ‘(Data.String.IsString
(f0 Char))’ from being solved.
Probable fix: use a type annotation to specify what ‘f0’ should be.
These potential instances exist:
instance (a ~ Char) => Data.String.IsString (Seq a)
-- Defined in ‘Data.Sequence.Internal’
instance [safe] (a ~ Tokens s, Data.String.IsString a, Eq a,
Stream s, Ord e) =>
Data.String.IsString (ParsecT e s m a)
-- Defined in ‘Text.Megaparsec.Internal’
instance (a ~ Char) => Data.String.IsString [a]
-- Defined in ‘Data.String’
...plus two instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘noneOf’, namely ‘"[]=:"’
In the first argument of ‘some’, namely ‘(noneOf "[]=:")’
In the second argument of ‘fmap’, namely ‘some (noneOf "[]=:")’
src/Data/Ini/Config/Raw.hs:215:12: error:
• Ambiguous type variable ‘f2’ arising from a use of ‘oneOf’
prevents the constraint ‘(Foldable f2)’ from being solved.
Probable fix: use a type annotation to specify what ‘f2’ should be.
These potential instances exist:
instance Foldable (Either a) -- Defined in ‘Data.Foldable’
instance Foldable Seq -- Defined in ‘Data.Sequence.Internal’
instance Foldable Seq.ViewL -- Defined in ‘Data.Sequence.Internal’
...plus five others
...plus 33 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In a stmt of a 'do' block: delim <- oneOf ":="
In the expression:
do pos <- getCurrentLine
key <- T.pack `fmap` some (noneOf "[]=:")
delim <- oneOf ":="
val <- T.pack `fmap` manyTill anySingle eol
....
In an equation for ‘pPair’:
pPair leading
= do pos <- getCurrentLine
key <- T.pack `fmap` some (noneOf "[]=:")
delim <- oneOf ":="
....
src/Data/Ini/Config/Raw.hs:215:18: error:
• Ambiguous type variable ‘f2’ arising from the literal ‘":="’
prevents the constraint ‘(Data.String.IsString
(f2 Char))’ from being solved.
Probable fix: use a type annotation to specify what ‘f2’ should be.
These potential instances exist:
instance (a ~ Char) => Data.String.IsString (Seq a)
-- Defined in ‘Data.Sequence.Internal’
instance [safe] (a ~ Tokens s, Data.String.IsString a, Eq a,
Stream s, Ord e) =>
Data.String.IsString (ParsecT e s m a)
-- Defined in ‘Text.Megaparsec.Internal’
instance (a ~ Char) => Data.String.IsString [a]
-- Defined in ‘Data.String’
...plus two instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘oneOf’, namely ‘":="’
In a stmt of a 'do' block: delim <- oneOf ":="
In the expression:
do pos <- getCurrentLine
key <- T.pack `fmap` some (noneOf "[]=:")
delim <- oneOf ":="
val <- T.pack `fmap` manyTill anySingle eol
....
src/Data/Ini/Config.hs:504: failure in expression `:{
data LocalConfig = LocalConfig
{ localUser :: Text }
deriving (Eq, Show)
:}'
expected:
but got:
^
<interactive>:35:19: error:
Not in scope: type constructor or class ‘Text’
Examples: 52 Tried: 2 Errors: 0 Failures: 1
Test suite test-doctest: FAIL
Test suite logged to: dist/test/config-ini-0.2.5.0-test-doctest.log
Test suite test-prewritten: RUNNING...
Passed: basic.ini
Passed: unicode.ini
Test suite test-prewritten: PASS
Test suite logged to: dist/test/config-ini-0.2.5.0-test-prewritten.log
Test suite test-ini-compat: RUNNING...
━━━ Test.Example ━━━
✓ propIniEquiv passed 100 tests.
✓ propRevIniEquiv passed 100 tests.
✓ propIniSelfEquiv passed 100 tests.
✓ 3 succeeded.
Test suite test-ini-compat: PASS
Test suite logged to: dist/test/config-ini-0.2.5.0-test-ini-compat.log
2 of 3 test suites (2 of 3 test cases) passed.
commercialhaskell/stackage#5632
config-ini-2.4.0 builds with megaparsec-9.0.0, so a hackage revision should be enough.
The ini-compat test suite fails to build, but that's #22, not megaparsec-related.
I don't understand why this is happening, but in this case I expect database
to be assigned the value foo
in the updated version, rather than janadb
:
*Data.Ini.Config.Bidir Data.Text> updateIniFile defaultConfig parseConfig (pack "[misc]\ndatabase = foo\n") defaultUpdatePolicy
Right "[misc]\ndatabase = janadb\n"
I have an INI section where the field set is not fixed, and so I'd like to parse that whole section into a Map
without a priori knowledge of the field names.
I'm envisioning something like this:
-- | Retrieve all fields in the section.
fieldMap :: SectionParser (Map Text Text)
-- | Retrieve all fields in the section and use the
-- supplied parser to parse each value, failing if the
-- parser fails to produce a value for any of the fields.
fieldMapOf :: (Text -> Either String a) -> SectionParser (Map Text a)
I took a look at this since I'm to the point where I'd like to start using bidir via Hackage, but it looks like at least merging master
to bidir
is non-trivial for me since I'm not familiar with what changed.
Until this is merged to Hackage, I can use bidir
, though.
$ cabal unpack config-ini-0.2.4.0
$ cd config-ini-0.2.4.0/
$ ls -l CHANGELOG.md README.md
-rwxr-xr-x. 1 petersen petersen 1197 Jan 1 1970 CHANGELOG.md
-rwxr-xr-x. 1 petersen petersen 8889 Jan 1 1970 README.md
It would be better to change the permissions in git from executable to regular files.
Apparently this can be done with git update-index --chmod=-x
.
Right now, the parse-only API parses what's provided and ignores everything else. This is probably not desirable in the long-term: for example, if I have a section defined as sectionMb "what"
and accidentally mistype my section name as [waht]
, then the parse will succeed (because the section is optional) but also silently ignore the user-provided section. This is not ideal in general.
My first-pass approach is probably going to be simply removing a section from the INI file structure as we parse it out, and ensuring that the final parsed structure is empty, but it's probably a good idea to think about whether this is sufficient or desirable in all cases, or whether there should be an opt-in way of ignoring sections instead.
Hi @aisamanra,
I know this is not the place for such a message, but since I don't know where to post, I'll abuse this Github tracker. Please delete this message after reading.
Your package config-ini is absolutely brilliant. I have used it for the first time in a project, and everything was a breeze. API is perfectly designed, it is sober and it works exactly as expected. I wish all packages were of the same quality.
I want to give you a big Thank you for that. Best regards,
Observed behavior: enabling default settings to be emitted only does so for sections that are already present in the input.
Expected/assumed behavior: that doing this without explicitly mentioning all sections would result in the emission of all other sections along with their defaults to form a fully-populated configuration.
(There are two sections in the specification that are not present in the output below.)
*Data.Ini.Config.Bidir Data.Text> updateIniFile defaultConfig parseConfig (pack "[misc]\ndatabase = foo\n") (UpdatePolicy True False CommentPolicyNone)
Right "[misc]\ndatabase = janadb\nbackend = any\ntmux-session-name = jana\nescrow-path = spdz-keys\nproject-root = .\nlog-directory = logs\n"
Steps to reproduce:
sectionOpt
to specify an optional section foo
foo
a setting bar
with a default valuefoo
section at allfoo
to some value other than the defaultExpected behavior: the new file now has a foo
section with a bar
setting indicating the new value.
Observed behavior: the new file still has no foo
section and defaults bar
to the default value.
Let's say I specify a section in my config parser (bidir
), and I specify that all of the fields to appear in that section have default values. Right now, it is only legal to omit the options but not the section header, so a legal config that falls back to all defaults must look like
[section]
but I'd love it if I could also omit the section header, too.
Testsuite build failure in current Stackage Nightly:
Building test suite 'test-ini-compat' for config-ini-0.2.4.0..
[1 of 1] Compiling Main ( test/ini-compat/Main.hs, dist/build/test-ini-compat/test-ini-compat-tmp/Main.o )
test/ini-compat/Main.hs:53:8: error:
• The constructor ‘I1.Ini’ should have 2 arguments, but has been given 1
• In the pattern: I1.Ini ini
In an equation for ‘lower’:
lower (I1.Ini ini)
= go (fmap go ini)
where
go hm = HM.fromList [(T.toLower k, v) | (k, v) <- HM.toList hm]
|
53 | lower (I1.Ini ini) = go (fmap go ini)
| ^^^^^^^^^^
test/ini-compat/Main.hs:74:3: error:
• Couldn't match type ‘[(Text, Text)] -> I1.Ini’ with ‘I1.Ini’
Expected type: GenT Data.Functor.Identity.Identity I1.Ini
Actual type: GenT
Data.Functor.Identity.Identity ([(Text, Text)] -> I1.Ini)
• In a stmt of a 'do' block: return (I1.Ini (HM.fromList ss))
In the expression:
do ss <- Gen.list (Range.linear 0 10)
$ do name <- textChunk
section <- Gen.list (Range.linear 0 10)
$ (,) <$> textChunk <*> textChunk
....
return (I1.Ini (HM.fromList ss))
In an equation for ‘mkIni’:
mkIni
= do ss <- Gen.list (Range.linear 0 10)
$ do name <- textChunk
....
return (I1.Ini (HM.fromList ss))
|
74 | return (I1.Ini (HM.fromList ss))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
test/ini-compat/Main.hs:74:19: error:
• Couldn't match type ‘HashMap Text Text’ with ‘[(Text, Text)]’
Expected type: HashMap Text [(Text, Text)]
Actual type: HashMap Text (HashMap Text Text)
• In the first argument of ‘I1.Ini’, namely ‘(HM.fromList ss)’
In the first argument of ‘return’, namely
‘(I1.Ini (HM.fromList ss))’
In a stmt of a 'do' block: return (I1.Ini (HM.fromList ss))
|
74 | return (I1.Ini (HM.fromList ss))
| ^^^^^^^^^^^^^^
The release of megaparsec 6 and limitations of cabal dependency resolution means it would help to have this bound bumped.
Hey @aisamanra, I hope you're doing well! I got this report a while back on Matterhorn and I'd love to know what you make of it. matterhorn-chat/matterhorn#761
The other two tests pass normally. Not sure what causes this :(
Running 3 test suites...
Test suite test-doctest: RUNNING...
src/Data/Ini/Config.hs:97:1: error:
Could not find module ‘Data.Ini.Config.Raw’
Use -v to see a list of the files searched for.
|
97 | import Data.Ini.Config.Raw
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Test suite test-doctest: FAIL
Hello
I try to put some JSON configuration in a key value.
datapath = {"dataPathHklQxQyQz":{"dataPathQxQyQzGeometry":{"tag":"GeometryPathUhv","geometryPathAxes":[{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/UHV_MU"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/UHV_OMEGA"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/UHV_DELTA"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/UHV_GAMMA"}]}}],"geometryPathWavelength":{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/Monochromator/wavelength"}]}}},"dataPathQxQyQzAttenuation":{"attenuationCoefficient":0,"attenuationOffset":2,"attenuationPath":{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/attenuation"}]}},"tag":"AttenuationPath"},"dataPathQxQyQzDetector":{"detectorPathImage":{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/xpad_image"}]}}}},"dataPathHklSample":{"tag":"SamplePath","contents":[{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/A"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/B"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/C"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Alpha"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Beta"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Gamma"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Ux"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Uy"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Uz"}]}}]}}
but when I try to read and parse this valid json objet, I have only this part of the text
"{\"dataPathHklQxQyQz\":{\"dataPathQxQyQzGeometry\":{\"tag\":\"GeometryPathUhv\",\"geometryPathAxes\":[{\"tag\":\"H5RootPath\",\"contents\":{\"tag\":\"H5GroupAtPath\",\"contents\":[0,{\"tag\":\"H5DatasetPath\",\"contents\":\"scan_data/UHV_MU\"}]"
so it seems to me that the parsing is broken when there is [] in the key.
I checked thaht removing all the '[' and ']' in the value solve this issue. But this is no more my JSON object :))
Cheers
Frederic
Hi!
It seems hacky to specify comments that span more than one line. Two naive attempts fail:
If I insert "\n" into the comment string, then the serialization would break at that spot, but not insert another "# "
If I would just add another & comment [...]
combinator, then the last one is taken and the first is ignored.
It works in the end by not only breaking with newline, but also adding "# " manually.
To illustrate with the example from the docs:
configParser' :: IniSpec Config ()
configParser' = do
section "NETWORK" $ do
cfHost .= field "host" string
& comment ["The desired hostname\n# some other comment line:"]
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.