Coder Social home page Coder Social logo

andygrunwald / go-gerrit Goto Github PK

View Code? Open in Web Editor NEW
93.0 93.0 38.0 498 KB

Go(lang) client/library for Gerrit Code Review

Home Page: https://godoc.org/github.com/andygrunwald/go-gerrit

License: MIT License

Go 99.86% Makefile 0.14%
code-review gerrit go golang hacktoberfest review

go-gerrit's Introduction

๐Ÿ‘‹ Hi, I'm Andy Grunwald

I am a Software Engineer and Engineering Manager from Germany with a focus on Backend-Systems and Infrastructure, Engineering Culture and People.

๐Ÿ‘ท Check out what I'm currently working on

๐Ÿ”ญ Latest releases I've contributed to

๐Ÿ”จ Latest Pull Requests I published

๐Ÿ“ My recent blog posts

๐Ÿ“ซ How to reach me

Twitter Follow

go-gerrit's People

Contributors

achew22 avatar afq984 avatar anandsudhir avatar andygrunwald avatar banksean avatar ben-clayton avatar bshi avatar chizhg avatar chyroc avatar dependabot-preview[bot] avatar dependabot[bot] avatar dmitshur avatar egonelbre avatar egorse avatar hanwen avatar kellyma2 avatar krzyzacy avatar lann avatar lukegb avatar mvdan avatar opalmer avatar pastel001 avatar perolausson avatar rmohr avatar seizens avatar srabraham avatar timwangmusic avatar victory460 avatar wwade avatar xobotyi avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar

go-gerrit's Issues

Contexts

Heya. Thanks for the library :)

Are there any plans for passing through context.Contexts? I'd like to use them for:

  1. Passing through deadlines in the X-Gerrit-Deadline header
  2. Pass some extra information about that one request to my custom *http.Client

client.Changes.SetReview fails with 401 Unauthorized

For the installation of Gerrit I'm using, the following code fails:

package main

import (
    "fmt"
    "github.com/andygrunwald/go-gerrit"
)

func main()  {
    client, err := gerrit.NewClient("<server>", nil)
    if err != nil {
        panic(err)
    }

    username := "<username>"
    password := "<password>"
    client.Authentication.SetDigestAuth(username, password)

    input := &gerrit.ReviewInput{
        Message: "FOOBAR",
        Drafts: "PUBLISH_ALL_REVISIONS",
    }
    info, response, err := client.Changes.SetReview(
        "<change-id>",
        "<revision>",
        input,
    )
    if err != nil {
        fmt.Println(response)
        fmt.Println(err)
    }
}

With this output:

API call to <server>/a/<change-id>/revisions/<revision>/review failed: 401 Unauthorized

Interestingly, it works using cURL:

curl -XPOST -v <server>/a/changes/<change-id>/revisions/<revision>/review' -H 'Content-Type: application/json' --data '{"drafts":"PUBLISH_ALL_REVISIONS","message":"FOOBAR"}' --digest -u <username>:<password>

And produces (content modified to show general request flow)

> POST /a/changes/<change-id>/revisions/<revision>/review HTTP/1.1
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 0
>
< HTTP/1.1 401 Unauthorized
< WWW-Authenticate: Digest [ redacted ]
> POST /a/changes/<change-id>/revisions/<revision>/review HTTP/1.1
> Authorization: Digest [redacted]
> Accept: */*
> Content-Type: application/json
> Content-Length: 52
>
* upload completely sent off: 52 out of 52 bytes
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Content-Length: 8
<
)]}'
{}

For some reason, /review seems to handle the contents of the request a little differently. I'm still trying to figure out why exactly. At first /review was returning a broken response but that turned out to be because we're reusing the request body for digest auth. With that being fixed by #12, I'm opening this issue to track the fix for /review.

A few quirks with the API

Now, this could be my own incompetency at doing golang, but there are a few things in the current API that I don't know how to handle best.

I'll take an example. If I query changes with LABELS/DETAILED-LABELS with the intent of checking if one or more changes have been approved then I was a bit stumped.

By approved I mean the final settings set on a change if a quality label is fully completed.

Here is what I had to do to check that:

func (c Change) Reviewed() bool {
    empty := gerrit.AccountInfo{}
    return empty != c.info.Labels.CodeReview.Approved   // info is a ChangeInfo
}

Now, this seems to be the case because that Approved thing is:


type AccountInfo struct {
    **AccountID int    `json:"_account_id"`**
    Name      string `json:"name,omitempty"`
    Email     string `json:"email,omitempty"`
    Username  string `json:"username,omitempty"`
}

If you can see AccountID will always be present, because it is never omitted.

Am I doing this totally wrong or is there a problem with the API? It seems very inelegant to me to compare with empty structs, especially since they aren't around when you need them.

Error in Projects.CreateProject: PUT a/projects/{name} invalid JSON in request

Here's a sample capture:

HTTP Request:

PUT /a/projects/test/ HTTP/1.1
Host: 192.168.160.1:8080
User-Agent: Go-http-client/1.1
Content-Length: 234
Accept: application/json
Authorization: Basic [redacted]
Content-Type: application/json
Accept-Encoding: gzip

{"name":"test","permissions_only":false,"create_empty_commit":true,"branches":["main"],"use_contributor_agreements":"","use_signed_off_by":"","create_new_change_for_all_not_in_target":"","use_content_merge":"","require_change_id":""}

HTTP Response:

HTTP/1.1 400 Bad Request
Date: Thu, 09 Sep 2021 02:15:48 GMT
X-Frame-Options: DENY
Content-Disposition: attachment
X-Content-Type-Options: nosniff
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 36

Invalid application/json in request

It looks like the right answer it to simply omitempty for use_contributor_agreements etc.

Only 500 changes are being pulled

Hi,

I am trying to pull change info from gerrit server. But I see only 500 max changes are coming.
Can you please help in how I can pull more than 500 changes at one time. Is there any specific attribute i need to mention in client or querychangeoptions?

Hello? <crickets>

Hopefully everyone is alive and well, no COVID-19 infections, etc.

I was just wondering if any future changes are planned on this project? Yes, I know, I can fork it, make my own changes, submit PRs, etc., but I would hate to waste time doing that if big changes were already in the works.

Basically, there have been many, many API changes since Gerrit 2.11. Current release is 3.1.3. I ran into one "breaking" change just trying to use this library with Gerrit 2.14:

'DELETE /changes/{change-id}/reviewers/{account-id}/votes/{label-id}'
'POST /changes/{change-id}/reviewers/{account-id}/votes/{label-id}/delete'

To delete a vote and send options (such as "notify: none"), you must use the POST method now. It used to work with the DELETE method, but they changed it to be more "REST-like".

They have also added several new REST endpoints.

All that said, this library helped me greatly with a project I was working on, so kudos there. :-)

So... if nobody is working on this library, I may give it a shot. Just wanted to see which way things are headed.

Thanks for working on this

This is a really great effort and seems to be quite mature for such a niche usecase. ๐Ÿ‘ Much appreciated.

Enhance Testing With Docker

Ideally it would be nice to test against a local instance of Gerrit. This should provide several new benefits for the project and its contributors:

  • Testing can be performed without depending on a remote instance of Gerrit. This isolates go-gerrit from upgrades, infrastructure changes and outages while also preventing tests from impacting someone else's Gerrit installation.
  • Setup/teardown steps could include creation of real changes, accounts, projects etc. This would mostly remove the need to mock responses within the tests.
  • It would provide a means for contributors to test against and identical installation of Gerrit for every test run.
  • Test can be written without having to worry about permission issues or the possibility of modifying production data.
  • In more advanced use cases, this setup could be used to test against specific configurations of Gerrit.

As part of this enhancement, the following modifications would be made to the code:

  • All tests would point at a configurable URL.
  • Docker-compose would be used on Travis to run Gerrit behind nginx. This will be more production like than just running Gerrit by itself and it also simplifies 'running gerrit' to a single command.

API-Methods: Return map/slice directly, rather than a pointer to one

From @shurcooL in #52

Modify both ListFiles and ListFilesReviewed to return map/slice directly, rather than a pointer to one. There doesn't appear to be any value in returning a pointer, it just makes the API harder to use. Slice/map are already reference types.

...

Thoughts welcome.

I know it's not consistent with other endpoints. But I couldn't bring myself to return *[]string or *map[string]FileInfo, since I was making a breaking API change to the method anyway.

I think we should change all other methods that similarly return pointers to maps to be return just map values. If that's the agreed direction, doing this first step here makes sense. Otherwise, I should revert it.

This issue is about to make it consistent for this library :)

Proposal: Support avatars plugin.

Per https://github.com/andygrunwald/go-gerrit#what-about-adding-code-to-support-the-rest-api-of-an-optional-plugin, making an issue first.

There appears to be some sort of "avatars" plugin or extension, which as I understand actually has multiple different implementations (e.g., avatars-gravatar, avatars-external, possibly others).

There's a dedicated has_avatars bool for it in PluginConfigInfo entity.

It seems to be popular, enough to be on all 3 of the Gerrit servers I tried:

$ curl -s 'https://gerrit-review.googlesource.com/config/server/info' | grep has_avatars
    "has_avatars": true,
$ curl -s 'https://go-review.googlesource.com/config/server/info' | grep has_avatars
    "has_avatars": true,
$ curl -s 'https://upspin-review.googlesource.com/config/server/info' | grep has_avatars
    "has_avatars": true,

As far as I can tell (but I haven't been able to verify this with full certainty), when that plugin/extension is on, the AccountInfo entity contains an additional undocumented avatars field:

$ curl 'https://gerrit-review.googlesource.com/accounts/1010008'
)]}'
{
  "_account_id": 1010008,
  "name": "Example Example",
  "email": "[email protected]",
  "avatars": [
    {
      "url": "https://lh3.googleusercontent.com/-qjpKeQViQbI/AAAAAAAAAAI/AAAAAAAAAAA/_CVwpoi1Y8Y/s26-p/photo.jpg",
      "height": 26
    },
    {
      "url": "https://lh3.googleusercontent.com/-qjpKeQViQbI/AAAAAAAAAAI/AAAAAAAAAAA/_CVwpoi1Y8Y/s32-p/photo.jpg",
      "height": 32
    },
    {
      "url": "https://lh3.googleusercontent.com/-qjpKeQViQbI/AAAAAAAAAAI/AAAAAAAAAAA/_CVwpoi1Y8Y/s100-p/photo.jpg",
      "height": 100
    }
  ]
}

So, is it okay to send a PR that'll add support for that field?

SetReview support for --project option

Hi,

I am looking for help with respect to finding equivalent of "--project" option of gerrit review command.

Right now I don't find "project" setting available as part of "ReviewInput".

gerrit review option -->

////////////////////////////////////////////////////////////////////////////////////////////////
--project
-p
Name of the project the intended changes are contained within. This option must be supplied before the commit SHA-1 in order to take effect.
///////////////////////////////////////////// EOM//////////////////////////////////////////////

Could you please let me know how to do this?

regards
Shankar

All DELETE api calls returns error 400 (Bad request)

Functions ChangesService.DeleteTopic, ChangesService.DeleteDraftChange returns error '400 Bad Requests'.

RCA:

In function NewRequest (inside gerrit.go), we set following headers:
Accept: application/json
Content-Type: application/json

This works for GET, POST api calls.
But as DELETE api calls does not need any content-type, they return 400.

QueryChanges converting query parameters into HTML encoding

I think there is a defect in QueryChanges or in the Gerrit REST API.

Consider a query that is constructed with multiple filters such as the one documented in the REST API:

https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes

In an example there under Request we find:

GET /changes/?q=is:open**+**owner:self&q=is:open+reviewer:self+-owner:self&q=is:closed+owner:self+limit:5&o=LABELS HTTP/1.0

Notice that the + is a literal + ascii.

However, when performing a query using the go-gerrit API, the + is translated into %2B

However, the Gerrit API does not recognise this and returns an empty result set.

Note that the query parameters sent in, are unrelated independent queries. They are not added together. I suspect this causes further confusion - potentially that is a separate bug, since I assumed something different and got some Json un-marshalling bugs ...

I think this needs a cleanup. Personally I would prefer a simpler interface and focus on just one query, but of course the question is how to model the query language. Is each query an AND or an OR etc.

Hope this makes sense to you.

Minimize source code in favour of generalisation

Most methods looking like this:

func (s *ProjectsService) GetDashboard(projectName, dashboardName string) (*DashboardInfo, *Response, error) {
    u := fmt.Sprintf("projects/%s/dashboards/%s", projectName, dashboardName)

    req, err := s.client.NewRequest("GET", u, nil)
    if err != nil {
        return nil, nil, err
    }

    v := new(DashboardInfo)
    resp, err := s.client.Do(req, v)
    if err != nil {
        return nil, resp, err
    }

    return v, resp, err
}

In the number of source methods this leads to thousands copied lines of code.
But only a small number of lines are different:

  • Init of v
  • Init of u

One idea is to unify them.
Here is a small example of ProjectsService.GetCommit how this can look like:

func (s *ProjectsService) GetCommit(projectName, commitID string) (*CommitInfo, *Response, error) {
    u := fmt.Sprintf("projects/%s/commits/%s", projectName, commitID)
    p := new(CommitInfo)
    v, resp, err := test(s, u, p)
    return v.(*CommitInfo), resp, err
}

func test(s *ProjectsService, u string, v interface{}) (interface{}, *Response, error) {
    req, err := s.client.NewRequest("GET", u, nil)
    if err != nil {
        return nil, nil, err
    }

    resp, err := s.client.Do(req, v)
    if err != nil {
        return nil, resp, err
    }

    return v, resp, err
}

Document missing OpenAPI/Swagger-Defintion/API-Spec

One thought would be if we can generate this client.
An API Spec file would be required for it.
A few discussions have been ongoing, but no progress/merge yet regarding this topic.
See:

The goal of this task:
Document this in the README, that this client lib is manually created + that new API endpoints must be added manually.

Optional specify changes query endpoint from /a/changes to /changes?

Currently with client.QueryChanges() call, I got

Get ${GERRIT_HOST}.googlesource.com/a/changes/?n=5\u0026o=CURRENT_REVISION\u0026q=project:test-infra: unsupported protocol scheme

while the API endpoint for us is suppose to be

${GERRIT_HOST}.googlesource.com/changes/

is there a way to get around?
Thanks!

Can't get the library working for repo names containing slashes

E.g. this call fails with 404 error:

branches, _, err := gr.Projects.ListBranches("my/project/name", &gerrit.BranchOptions{})

it seems it keeps generating unescaped URLs like http://mygerritserver/r/a/projects/my/project/name/branches/ which are not valid for Gerrit.

When debugging the code, I found that the function buildURLForRequest(urlStr string) (string, error) unescapes the project part.

Proposal: Use Timestamp type for all time fields.

Right now, go-gerrit uses string type for all time fields. E.g.:

// ChangeInfo entity contains information about a change.
type ChangeInfo struct {
	...
	Created   string `json:"created"`
	Updated   string `json:"updated"`
	Submitted string `json:"submitted,omitempty"`
	...
}

Gerrit API documents time format at https://gerrit-review.googlesource.com/Documentation/rest-api.html#timestamp as:

Timestamps are given in UTC and have the format "'yyyy-mm-dd hh:mm:ss.fffffffff'" where "'ffffffffff'" represents nanoseconds.

I propose we make a breaking API change and start using a Timestamp type for all time fields. E.g., something like this (details to be tweaked):

// ChangeInfo entity contains information about a change.
type ChangeInfo struct {
	...
	Created   Timestamp `json:"created"`
	Updated   Timestamp `json:"updated"`
	Submitted Timestamp `json:"submitted,omitempty"`
	...
}

// Timestamp represents an instant in time. It encodes to and from JSON as a Gerrit timestamp in UTC.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api.html#timestamp
type Timestamp struct {
	time.Time
}

func (t *Timestamp) UnmarshalJSON(b []byte) error {
	// Ignore null, like in the main JSON package.
	if string(b) == "null" {
		return nil
	}
	if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
		return fmt.Errorf("failed to unmarshal non-string value %q as a Gerrit time", b)
	}
	tm, err := time.Parse(timeLayout, string(b[1:len(b)-1]))
	if err != nil {
		return err
	}
	t.Time = tm
	return nil
}

func (t Timestamp) MarshalJSON() ([]byte, error) { ... }

// Gerrit's timestamp layout is like time.RFC3339Nano, but with a space instead
// of the "T", and without a timezone (it's always in UTC).
// See https://gerrit-review.googlesource.com/Documentation/rest-api.html#timestamp.
const timeLayout = "2006-01-02 15:04:05.999999999"

I think making this change would significantly improve the usability of go-gerrit API.

It allows immediately using the time.Time methods on such fields, without having to parse each timestamp string and check for errors (something that should never happen, so factoring out the error checking into the go-gerrit library is very helpful at reducing boilerplate in user code).

go-github follows a similar pattern, it has a github.Timestamp type.

Wrong error message by 400 Bad Request

$ curl https://review.typo3.org/changes/\?n\=500\&o\=DETAILED_ACCOUNTS\&o\=LABELS\&o\=WEB_LINKS\&o\=ALL_FILES\&o\=MESSAGES\&o\=CHANGE_ACTIONS\&o\=REVIEWED\&o\=WEB_LINKS\&o\=COMMIT_FOOTERS\&o\=ALL_REVISIONS\&o\=DOWNLOAD_COMMANDS\&o\=CURRENT_COMMIT\&o\=ALL_COMMITS\&o\=CURRENT_FILES\&o\=CURRENT_REVISION\&o\=DETAILED_LABELS\&q\=project%3ATYPO3CMS%2FExtensions%2Fvidi
"COMMIT_FOOTERS" is not a valid value for "-o"

Only responds

API call to https://review.typo3.org/changes/?n=500&o=DETAILED_ACCOUNTS&o=LABELS&o=WEB_LINKS&o=ALL_FILES&o=MESSAGES&o=CHANGE_ACTIONS&o=REVIEWED&o=WEB_LINKS&o=COMMIT_FOOTERS&o=ALL_REVISIONS&o=DOWNLOAD_COMMANDS&o=CURRENT_COMMIT&o=ALL_COMMITS&o=CURRENT_FILES&o=CURRENT_REVISION&o=DETAILED_LABELS&q=project%3ATYPO3CMS%2FExtensions%2Fvidi failed: 400 Bad Request

But i expect "COMMIT_FOOTERS" is not a valid value for "-o" as a error message.

Tagged Releases

So after using go-gerrit for a while I've been starting to think we should be tagging releases. There's a few reasons for this:

  • Vendoring can take advantage of this and lock down to specific versions of go-gerrit. You can technically do this with revisions too but tags are easier to work with and more obvious.
  • There's no a good way to handle breaking changes at the moment. For example, GetReflog can take from and to url parameters but the current implementation of go-gerrit does not support this. The change could be made in master now but it would be a breaking change and it does not feel appropriate to make a new function.
  • There have been several contributions for everything from new features to major bug fixes. It would be nice, though not a requirement, to keep a small changelog of these kinds of changes so when you upgrade from 0.1.0 to 0.1.1 you have a better understanding of changes between versions.

Labels handling hard-coded to assume only "default" labels are in use

While verifying the change for issue #19 I realised that the current Labels handling seem hard coded to assume that only CodeReview and Verified exists as labels. I am wondering what would have happened if I had some other label defined for a project?

I am thinking about this code in changes.go:

// Labels entity contains the labels Verified & CodeReview, always corresponding to the current patch set.
type Labels struct {
    Verified   LabelInfo `json:"Verified,omitempty"`
    CodeReview LabelInfo `json:"Code-Review,omitempty"`
}

I actually like the way this is, but of course it would be a problem if I had some other label. I presume this means that Labels should be a map of LabelInfo instead, with keys being Verified, CodeReview, ...?

Simpler fix for credentials with characters such as '/'.

Thanks for describing the problem in great detail in PR #39 @opalmer.

I suspect there may be a much simpler fix, which I'd like to discuss. Consider this comment:

go-gerrit/gerrit.go

Lines 101 to 104 in 70bbb05

// Depending on the contents of the username and password the default
// url.Parse may not work. The below is an example URL that
// would end up being parsed incorrectly with url.Parse:
// http://admin:ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg@localhost:38607

If I'm not mistaken, that URL gets parsed correctly by url.Parse. It's just that the URL itself is not correctly escaped, so it doesn't produce the results you want.

Let's use url.URL.String method to construct a URL with "http" schema, "admin" username, "ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg" password, and "localhost:38607" host:

u := &url.URL{
	Scheme: "http",
	User:   url.UserPassword("admin", "ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg"),
	Host:   "localhost:38607",
}
fmt.Println(u.String())

// Output: http://admin:ZOSOKjgV%2FkgEkN0bzPJp+oGeJLqpXykqWFJpon%2FCkg@localhost:38607

(See on playground: https://play.golang.org/p/M3cq7xWI2eE.)

Note that the / character in the password gets escaped to %2F.

When we parse that URL with url.Parse, it produces the expected results:

u, err := url.Parse("http://admin:ZOSOKjgV%2FkgEkN0bzPJp+oGeJLqpXykqWFJpon%2FCkg@localhost:38607")
if err != nil {
	log.Fatalln(err)
}
fmt.Println(u.Scheme)
fmt.Println(u.User.Username())
fmt.Println(u.User.Password())
fmt.Println(u.Host)

// Output:
// http
// admin
// ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg true
// localhost:38607

(See on playground: https://play.golang.org/p/01GMpYMMzsw.)

Notably, the original "ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg" password is preserved.

So, I believe as long as the URL is correctly escaped, the logic added in #39 isn't needed and can be reverted. That would simplify the code. What do you think @opalmer?

Move coverage to codecov.io?

So on other projects I've moved from coveralls.io to codecov.io for a few reasons:

  • The web interface for viewing coverage is more intuitive and generally provides more relevant information up front instead of having to dig deeper.
  • Improved build reliability. I've seen several cases where backend issues with coveralls.io causes a build to fail (can't find repository, remote servers not responding, etc). Restarting a build attempt fixes the issue usually but it's still annoying especially for non-collaborators.
  • coveralls.io comments in a PR don't provide a great deal of context especially at the diff level:
    screen shot 2016-10-16 at 10 24 22 am
    compared to codecov.io:
    screen shot 2016-10-16 at 10 19 32 am
  • Coveralls adds a new comment for every commit, codecov makes one comment then edits it as you add more commits. This was configurable at one point for coveralls.io but it was buggy and didn't always work (comment not updated or a new comment was added anyway).
  • In my experience codecov.io fixes bugs faster and has better overall support.

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.