enkessler / cuke_modeler Goto Github PK
View Code? Open in Web Editor NEWA gem to model a Cucumber test suite.
License: MIT License
A gem to model a Cucumber test suite.
License: MIT License
Hi Eric,
I would like to suggest implementing #inspect
on the model objects with a terse return value as a quality of life improvement.
Currently, if I do something stupid and cause an exception to be raised, I'll get the full model rendered through #to_s
which will swamp the console and obscure the exception message. This hinders experimentation in irb.
Hello,
I was puzzled about the 'cucumber', 2.2.0'
specification in the Gemfile, so I've patched it to be >= 2.2.0
and tried running the default
rake target, and to my surprises it runs fine:
starting phase `check'
Clearing report directory...
Running tests...
Running RSpec tests...
Testing with dialect 'en-Scouse'...
Run options: exclude {:wip=>true}
...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Finished in 2.26 seconds (files took 1.02 seconds to load)
1343 examples, 0 failures
Coverage report generated for rspec_tests to /tmp/guix-build-ruby-cuke-modeler-3.19.0.drv-0/source/reports/coverage. 1036 / 1100 LOC (94.18%) covered.
Lcov style coverage report generated for rspec_tests to /tmp/guix-build-ruby-cuke-modeler-3.19.0.drv-0/source/reports/coverage/lcov/lcov.info
All RSpec tests passing.
Running Cucumber tests...
`/homeless-shelter` is not a directory.
Bundler will use `/tmp/guix-build-ruby-cuke-modeler-3.19.0.drv-0/bundler20230316-33-1d1yhwp33' as your home directory temporarily.
/gnu/store/frhaqvx1i79a1y4w8xfxr2s3c0539lzb-ruby-mime-types-3.1/lib/ruby/vendor_ruby/gems/mime-types-3.1/lib/mime/types/logger.rb:28: warning: `_1' is reserved for numbered parameter; consider another name
/gnu/store/frhaqvx1i79a1y4w8xfxr2s3c0539lzb-ruby-mime-types-3.1/lib/ruby/vendor_ruby/gems/mime-types-3.1/lib/mime/types/logger.rb:28: warning: `_2' is reserved for numbered parameter; consider another name
/gnu/store/frhaqvx1i79a1y4w8xfxr2s3c0539lzb-ruby-mime-types-3.1/lib/ruby/vendor_ruby/gems/mime-types-3.1/lib/mime/types/logger.rb:28: warning: `_3' is reserved for numbered parameter; consider another name
Using the default, html and wip_filter profiles...
...........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
102 scenarios (102 passed)
539 steps (539 passed)
0m1.257s
Formatter SimpleCov::Formatter::HTMLFormatter failed with Errno::EACCES: Permission denied @ rb_sysopen - /tmp/guix-build-ruby-cuke-modeler-3.19.0.drv-0/source/reports/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png (/gnu/store/j4z07lyi1ykk8bc68h1p4bpj1il9dn3f-ruby-2.7.4/lib/ruby/2.7.0/fileutils.rb:1415:in `initialize')
Lcov style coverage report generated for cucumber_tests, rspec_tests to /tmp/guix-build-ruby-cuke-modeler-3.19.0.drv-0/source/reports/coverage/lcov/lcov.info
All Cucumber tests passing.
All tests passing!
phase `check' succeeded after 7.6 seconds
These are the direct dependency versions in my environment:
[email protected] [email protected] [email protected]
+ [email protected] [email protected] [email protected] [email protected]
Perhaps it could be updated?
Thanks!
I recently upgraded from 0.4.1 to 1.4.3 and am getting the following error
NoMethodError: undefined method `row_elements' for #
<CukeModeler::Example:0x00000002f75560>
from vendor/bundle/ruby/gems/cuke_slicer-2.0.0/lib/cuke_slicer/helpers/extraction_helpers.rb:33:
in `block (2 levels) in extract_runnable_elements'
This seems to happen only when there is a Scenario Outline present in test_directory
in
=> 11: tests = CukeSlicer::Slicer.new.slice(test_directory, filters, :file_line)
(this is something we wrote)
Thanks for the work, this looks great.
I was actually working on building a decent cucumber parser myself and stumbled upon this project ๐
I'd love to use this for a project, but am running into some issues where a dependency conflict seems to break the cucumber runner, both when using spring as well as using parallel_tests
:
error message:
|| [TEST PROF INFO] Spring detected
|| set_config
|| ------------
||
|| (1 row)
||
/Users/alexanderjeurissen/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/gherkin/ast_builder.rb|81 error| undefined method `keys' for #<Cucumber::Messages::GherkinDocument::Feature:0x00007f8c4cd04f78>
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/gherkin/ast_builder.rb:32:in `initialize'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/gherkin/ast_builder.rb:125:in `initialize'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/gherkin/ast_builder.rb:106:in `new'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/gherkin/ast_builder.rb:106:in `feature'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/gherkin/ast_builder.rb:17:in `feature'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/gherkin/parser.rb:32:in `document'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core.rb:30:in `block in parse'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core.rb:29:in `each'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core.rb:29:in `parse'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core.rb:21:in `compile'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-3.2.0/lib/cucumber/runtime.rb:75:in `run!'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-3.2.0/lib/cucumber/cli/main.rb:34:in `execute!'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/cucumber-3.2.0/bin/cucumber:9:in `<main>'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.9/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.9/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:318:in `block in load'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:291:in `load_dependency'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:318:in `load'
|| Development/project/bin/cucumber:8:in `<main>'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.9/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.9/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:318:in `block in load'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:291:in `load_dependency'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:318:in `load'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/spring-2.1.1/lib/spring/command_wrapper.rb:40:in `call'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/spring-2.1.1/lib/spring/application.rb:220:in `block in serve'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/spring-2.1.1/lib/spring/application.rb:180:in `fork'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/spring-2.1.1/lib/spring/application.rb:180:in `serve'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/spring-2.1.1/lib/spring/application.rb:145:in `block in run'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/spring-2.1.1/lib/spring/application.rb:139:in `loop'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/spring-2.1.1/lib/spring/application.rb:139:in `run'
|| .rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/spring-2.1.1/lib/spring/application/boot.rb:19:in `<top (required)>'
|| .rbenv/versions/2.6.6/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
|| .rbenv/versions/2.6.6/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
|| -e:1:in `<main>'
content of bin/cucumber
:
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('cucumber', 'cucumber')
The issue also occurs when invoking cucumber without spring.
relevant content of Gemfile.lock
:
cucumber (3.2.0)
builder (>= 2.1.2)
cucumber-core (~> 3.2.0)
cucumber-expressions (~> 6.0.1)
cucumber-wire (~> 0.0.1)
diff-lcs (~> 1.3)
gherkin (~> 5.1.0)
multi_json (>= 1.7.5, < 2.0)
multi_test (>= 0.1.2)
cucumber-core (3.2.1)
backports (>= 3.8.0)
cucumber-tag_expressions (~> 1.1.0)
gherkin (~> 5.0)
cucumber-expressions (6.0.1)
cucumber-gherkin (16.0.0)
cucumber-messages (~> 13.2, >= 13.2.1)
cucumber-messages (13.2.1)
protobuf-cucumber (~> 3.10, >= 3.10.8)
cucumber-rails (1.8.0)
capybara (>= 2.12, < 4)
cucumber (>= 3.0.2, < 4)
mime-types (>= 2.0, < 4)
nokogiri (~> 1.8)
railties (>= 4.2, < 7)
cucumber-tag_expressions (1.1.1)
cucumber-wire (0.0.1)
cuke_linter (1.1.0)
cuke_modeler (>= 1.5, < 4.0)
cuke_modeler (3.5.0)
cucumber-gherkin (< 17.0)
protobuf-cucumber (3.10.8)
activesupport (>= 3.2)
middleware
thor
thread_safe
spring-commands-cucumber (1.0.1)
spring (>= 0.9.1)
cucumber-rails (= 1.8.0)
Seems I'm not alone in this: https://stackoverflow.com/questions/62952103/undefined-method-keys-for-cucumbermessagesgherkindocumentfeature0x080
I raise an issue with parallel_tests gem here (grosser/parallel_tests#816), but it might be related to the cucumber-gherkin dependency version in cuke_modeler.
Bundler could not find compatible versions for gem "cucumber-gherkin":
In Gemfile:
cucumber (= 6.0.0) was resolved to 6.0.0, which depends on
cucumber-gherkin (>= 18.1.0, ~> 18.1)
cuke_modeler (~> 3.0) was resolved to 3.7.0, which depends on
cucumber-gherkin (< 18.0)
I'm writing a custom rule which will catch the commented tags using https://github.com/enkessler/cuke_linter.
Sample scenario
Feature: Sample feature
@tag #@commented_tag
Scenario: Sample scenario
Given I do something
And It's done
parsed model snippet
@tags=
[#<CukeModeler::Tag:0x00007fcb0ecb9518
@name="@tag #",
@parent_model=#<CukeModeler::Scenario:0x00007fcb0ecba9e0 ...>,
@parsing_data={:type=>:Tag, :location=>{:line=>3, :column=>3}, :name=>"@tag #"},
@source_line=3>,
#<CukeModeler::Tag:0x00007fcb0ecb9360
@name="@commented_tag",
@parent_model=#<CukeModeler::Scenario:0x00007fcb0ecba9e0 ...>,
@parsing_data={:type=>:Tag, :location=>{:line=>3, :column=>9}, :name=>"@commented_tag"},
@source_line=3>]>]>,
it parsed it as "@ tag #" and "@commented_tag", I want to ban #commented_tag.
While I can work around the currently parsed model, just wanted to know if it's worth making changes in cuke_modeler so it shows the tag as "#@commented_tag"?
cuke_modeler
is the successor of cuke_analytics
.
In the readme of cuke_analytics
you state as much, with the side-note that the only reason to use the former is for its step_definition
support.
I think it would be beneficial if one could use a single gem for all their cucumber modeling needs so we can ๐ cuke_analytics
for good.
Would you be open for a PR that adds support for step definitions ?
My preliminary thinking is that as a first step step_definitions
would be a child of the Directory
class. This would enable for adding basic linting for step definitions in cuke_linter
.
Given that cucumber steps by default live in subdirectory of the base feature directory, this should work in most standard setups. If not, getting your hand on modeled step_definitions is only one Directory.new(setp_definition_path)
away.
WDYT ?
Cuke modeler can't explodes when parsing the feature text below.
@wip @entity_abstract @allow_base_target @document
Feature: Abstract Entity. A summary of the contents of a book, article, or formal speech
Schema Rule: abstract must have abstract context and (abstract section OR abstract sentence)
Schema Rule: abstract may have language, machine translated flag
Schema Rule: only 1 abstract section or abstrace sentence is present at a time
Xfrm Rule: if abstract present in source, present in target
Xfrm Rule: abstract context is passthrough
Xfrm Rule: Language group is look up
Xfrm Rule: machine translated flag= if Y passthrough. If N, drop attribute
Xfrm Rule: if abstract unstructured [No keyword sections in source - See DD] use abstract sentence
Xfrm Rule: if abstract structured [Has keyword sections in source - See DD] use abstract section
# TODO: Is there a rule on how to order? Which abstract is first? Based on order in source?
Sources: Casdocs (non-patents, patents), medline
References: None for this entity
@schema_test
Scenario Outline: Validate document can have 0 or many abstracts
Given I have a source with of abstracts
Then the corresponding target record also has of abstracts
Examples:
| number | document |
| 0 | casdoc |
| 1 | casdoc |
| more than 2 | casdoc |
| 0 | patent |
| 1 | patent |
| more than 2 | patent |
| 0 | medline |
| 1 | medline |
| more than 2 | medline |
Error output:
Error encountered while parsing 'features/domains/document/document_biblio/document_abstract/document_abstract.feature'
Gherkin::CompositeParserException - Parser errors:
(13:3): expected: #EOF, #Comment, #BackgroundLine, #TagLine, #ScenarioLine, #ScenarioOutlineLine, #Empty, got 'Sources:
Casdocs (non-patents, patents), medline'
(14:3): expected: #EOF, #Comment, #BackgroundLine, #TagLine, #ScenarioLine, #ScenarioOutlineLine, #Empty, got 'Reference
s: None for this entity'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/parsing.rb:27:in rescue in parse_text' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/parsing.rb:24:in
parse_text'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/feature_file.rb:67:in parse_file' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/feature_file.rb:24:in
initialize'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in new' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in
build_child_element'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:76:in block in build_director y' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in
each'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in build_directory' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:30:in
initialize'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in new' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in
build_child_element'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:74:in block in build_director y' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in
each'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in build_directory' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:30:in
initialize'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in new' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in
build_child_element'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:74:in block in build_director y' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in
each'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in build_directory' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:30:in
initialize'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in new' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in
build_child_element'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:74:in block in build_director y' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in
each'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in build_directory' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:30:in
initialize'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in new' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/containing.rb:11:in
build_child_element'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:74:in block in build_director y' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in
each'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:69:in build_directory' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/cuke_modeler-0.4.1/lib/cuke_modeler/directory.rb:30:in
initialize'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/bundler/gems/cuke_cataloger-6ceabc48425d/lib/cuke_cataloger/unique_test_case_tagger.
rb:105:in new' c:/casnc/Ruby22/lib/ruby/gems/2.2.0/bundler/gems/cuke_cataloger-6ceabc48425d/lib/cuke_cataloger/unique_test_case_tagger. rb:105:in
set_test_suite_model'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/bundler/gems/cuke_cataloger-6ceabc48425d/lib/cuke_cataloger/unique_test_case_tagger.
rb:69:in validate_test_ids' C:/casnc/repos/edh-tests/.git-hooks/pre_commit/cuke_cataloger_check.rb:9:in
run'
c:/casnc/Ruby22/lib/ruby/gems/2.2.0/gems/overcommit-0.34.2/lib/overcommit/hook/base.rb:45:in `block in run_and_transform
'
Hey! Thanks for the abstraction of the gherkin parser here.
Since this is a gem to make life easier when working with gherkin feature files.
Would it be out of scope to add functionality to e.g. add tags, remove tags, replace tags, change feature title/description.
For example we could do things like:
file = CukeModeler::FeatureFile.new(filename)
file.feature.name = "Updated #{file.feature.name}"
file.feature.add_tag("ISSUE-5")
# or file.feature.replace_tag("id:5", "ISSUE-5")
file.write(filename)
This might also be worthwhile for cuke_tagger and cuke_cataloger. I see that you have a lot of custom code to modify the gherkin feature files.
What do you think?
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.