groovy有什么用
使用GitHub API,Groovy和GraphViz根据存储库的观察者确定,解释和呈现GitHub用户之间的关系图。 最终结果可能看起来像这样。
GitHub V3 API
您可以在此处找到GitHub V3 API的完整文档。 他们非常出色地记录了各种端点及其行为,并充分展示了curl的用法。 出于本文的目的,我正在进行的API调用是不需要身份验证的简单GET请求。 特别是,我的目标是两个特定的端点:特定用户的存储库和存储库的监视程序。
API的局限性
尽管从V2 API的每小时50个请求速率限制进行了巨大的升级,但我发现在收集数据的同时耗尽V3 API每小时提供的5000个请求相当容易。 幸运的是,GitHub的每个响应中都包含一个方便的X-RateLimit-Remaining标头,我们可以使用它检查限制。 这使我们能够在请求用完之前停止处理,此后GitHub将为每个请求返回错误。 对于每个用户,我们检查一个URL来找到他们的存储库,对于每个存储库,执行一个单独的请求以查找所有观察者。 在执行这些请求时,以我自己的GitHub帐户为中心,我能够收集有关1143个用户的存储库信息,并找到31142个总观察者,其中18023个在收集的数据中是唯一的。 这一直是个残破的数字,因为在达到速率限制后,队列中尚待处理的节点比已经遇到的要多得多。 我本人总共只有31个存储库监视程序,但是在图中显示,我们发现像igrigorik这样的用户,igrigorik是Google的一名员工,有529个存储库监视程序,这往往会使结果有些偏离。 遗憾的是,最终结果是这里提供的数据还远远不够完整,但这并不意味着可视化就没有意思。
Groovy和HttpBuilder
Groovy和HttpBuilder dsl抽象了处理HTTP连接的大多数细节。 我正在构建的图形从一个GitHub中央用户开始,并将该用户链接到当前正在监视其存储库之一的每个人。 这需要单个GET请求为给定用户加载所有存储库,并且每个存储库都有一个GET请求以查找观察者。 这两个HTTP操作非常容易使用HttpClient周围的HttpBuilder包装器与Closures封装在一起。 每个调用都返回X-RateLimit-Remaining值和所请求的数据。 HttpBuilder的配置如下所示:
1
2final String rootUrl = 'https://api.github.com' final HTTPBuilder builder = new HTTPBuilder(rootUrl)
在GitHub api url上创建并固定了builder对象,从而简化了以后调用的语法。 现在,我们定义两个闭包,每个闭包都针对特定的url,并从JSON响应(已经由HttpBuilder自动取消编组)中提取适当的数据。 findWatchers Closure的逻辑更多一点,可以删除重复的条目,并将用户本身从列表中排除,因为默认情况下,GitHub为所有拥有自己存储库的用户记录一个自引用链接。
1
2
3
4
5
6
7
8
9
10
11final String RATE_LIMIT_HEADER = 'X-RateLimit-Remaining' final Closure findReposForUser = { HTTPBuilder http, username -> http.get(path: "/users/$username/repos", contentType: JSON) { resp, json -> return [resp.headers[RATE_LIMIT_HEADER].value as int, json.toList()] } } final Closure findWatchers = { HTTPBuilder http, username, repo -> http.get(path: "/repos/$username/$repo/watchers", contentType: JSON) { resp, json -> return [resp.headers[RATE_LIMIT_HEADER].value as int, json.toList()*.login.flatten().unique() - username] } }
从这些数据中,我们仅对保持用户名->观察者的简单映射感兴趣(目前),我们可以轻松地将其映射为JSON对象并存储在文件中。 可以使用以下代码从命令行运行用于加载数据的完整Groovy脚本代码,或者通过调用groovy https://raw.github.com/gist/2468052/5d536c5a35154defb5614bed78b325eeadbdc1a7/从GitHub gist在命令行上远程执行repos.groovy {用户名}。 无论哪种情况,您都应该输入您想要以图表为中心的用户名。 结果将输出到工作目录中名为“ reposOutput.json”的文件中。 请耐心等待,因为这将需要一段时间。 在处理每个用户时,进度将输出到控制台,因此您可以继续进行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.5.2') import groovy.json.JsonBuilder import groovyx.net.http.HTTPBuilder import static groovyx.net.http.ContentType.JSON final rootUser = args[0] final String RATE_LIMIT_HEADER = 'X-RateLimit-Remaining' final String rootUrl = 'https://api.github.com' final Closure<Boolean> hasWatchers = {it.watchers > 1} final Closure findReposForUser = { HTTPBuilder http, username -> http.get(path: "/users/$username/repos", contentType: JSON) { resp, json -> return [resp.headers[RATE_LIMIT_HEADER].value as int, json.toList()] } } final Closure findWatchers = { HTTPBuilder http, username, repo -> http.get(path: "/repos/$username/$repo/watchers", contentType: JSON) { resp, json -> return [resp.headers[RATE_LIMIT_HEADER].value as int, json.toList()*.login.flatten().unique() - username] } } LinkedList nodes = [rootUser] as LinkedList Map<String, List> usersToRepos = [:] Map<String, List<String>> watcherMap = [:] boolean hasRemainingCalls = true final HTTPBuilder builder = new HTTPBuilder(rootUrl) while(!nodes.isEmpty() && hasRemainingCalls) { String username = nodes.remove() println "processing $username" println "remaining nodes = ${nodes.size()}" def remainingApiCalls, repos, watchers (remainingApiCalls, repos) = findReposForUser(builder, username) usersToRepos[username] = repos hasRemainingCalls = remainingApiCalls > 300 repos.findAll(hasWatchers).each{ repo -> (remainingApiCalls, watchers) = findWatchers(builder, username, repo.name) def oldValue = watcherMap.get(username, [] as LinkedHashSet) oldValue.addAll(watchers) watcherMap[username] = oldValue nodes.addAll(watchers) nodes.removeAll(watcherMap.keySet()) hasRemainingCalls = remainingApiCalls > 300 } if(!hasRemainingCalls) { println "Stopped with $remainingApiCalls api calls left." println "Still have not processed ${nodes.size()} users." } } new File('reposOutput.json').withWriter {writer -> writer << new JsonBuilder(watcherMap).toPrettyString() }
JSON文件包含非常简单的数据,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13"bmuschko": [ "claymccoy", "AskDrCatcher", "roycef", "btilford", "madsloen", "phaggood", "jpelgrim", "mrdanparker", "rahimhirani", "seymores", "AlBaker", "david-resnick", ...
现在,我们需要获取这些数据并将其转换为GraphViz可以理解的表示形式。 我们还将添加有关每个用户的观察者数量的信息,以及返回其GitHub页面的链接。
以点格式生成GraphViz文件
GraphViz是用于生成图形的流行框架。 它的基石是一种简单格式,用于在简单的文本文件(通常称为“点”文件)中描述有向图,并结合了多种不同的布局以显示图。 出于这篇文章的目的,我将在图形中描述以下内容:
- 从每个观察者到正在监视其存储库的用户的边缘。
- 每个节点上的标签,其中包括用户名和所有存储库的监视者计数。
- 每个节点上用户GitHub页面的嵌入式HTML链接。 通过将该节点涂成红色来突出显示图中的初始用户。
- 为链接所有具有相同观察者数量的用户的节点分配“等级”属性。
我用于创建“点”文件的脚本几乎只是蛮力字符串处理,完整的源代码可以作为要点使用,但是这里是有趣的部分。 首先,加载在最后一步中输出的JSON文件; 将其转换为地图结构非常简单:
1
2
3
4def data new File(filename).withReader {reader -> data = new JsonSlurper().parse(reader) }
从此数据结构中,我们可以提取特定的详细信息,并按每个用户的观察者数量对所有内容进行分组。
1
2
3
4
5
6
7
8println "Number of mapped users = ${data.size()}" println "Number of watchers = ${data.values().flatten().size()}" println "Number of unique watchers = ${data.values().flatten().unique().size()}" //group the data by the number of watchers final Map groupedData = data.groupBy {it.value.size()}.sort {-it.key} final Set allWatchers = data.collect {it.value}.flatten() final Set allUsernames = data.keySet() final Set leafNodes = allWatchers - allUsernames
给定此数据,我们将创建具有样式详细信息的单个节点,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13StringWriter writer = new StringWriter() groupedUsers.each {count, users -> users.each { username, watchers -> def user = "t"$username"" def attrs = generateNodeAttrsMemoized(username, count) def rootAttrs = "fillcolor=red style=filled $attrs" if (username == rootUser) { writer << "$user [$rootAttrs];n" } else { writer << "$user [$attrs ${extraAttrsMemoized(count, username)}];n" } } }
这将生成如下所示的节点和边缘描述:
1
2
3
4
5
6
7
8
9... "gyurisc" [label="gyurisc = 31" URL="https://github.com/gyurisc" ]; "kellyrob99" [fillcolor=red style=filled label="kellyrob99 = 31" URL="https://github.com/kellyrob99"]; ... "JulianDevilleSmith" -> "cfxram"; "rhyolight" -> "aalmiray"; "kellyrob99" -> "aalmiray"; ...
如果已经创建了JSON数据,则可以在同一目录中运行此命令以生成GraphViz点文件:groovy https://raw.github.com/gist/2475460/78642d81dd9bc95f099e0f96c3d87389a1ef6967/githubWatcherDigraphGenerator.groovy {username} reposOutput .json。 这将在该目录中创建一个名为“ reposDigraph.dot”的文件。 从那里开始,最后一步是将图形定义解释为图像。
将“点”文件转换为图像
我一直在寻找一种快速简便的方法来从同一模型快速生成多个可视化文件进行比较,并决定使用GPar并发生成它们。 我们在这里必须格外小心,因为某些布局/格式组合可能需要相当大的内存和CPU –在最坏的情况下,多达2GB的内存和处理时间在一个小时的范围内。 我的建议是坚持使用sfdp和twopi(请参见此处的在线文档)布局,以使图形的大小类似于此处所述。 如果您正在寻找具有完整细节的巨大,令人惊叹的图形,则可以期望png图像的重量在150MB以北,而相应的svg文件将小于10MB。 该Groovy脚本依赖于已安装GraphViz命令行“点”可执行文件,练习六个可用的布局算法并同时使用四个来生成png和svn文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import groovyx.gpars.GParsPool def inputfile = args[0] def layouts = [ 'dot', 'neato', 'twopi', 'sfdp', 'osage', 'circo' ] //NOTE some of these will fail to process large graphs def formats = [ 'png', 'svg'] def combinations = [layouts, formats].combinations() GParsPool.withPool(4) { combinations.eachParallel { combination -> String layout = combination[0] String format = combination[1] List args = [ '/usr/local/bin/dot', "-K$layout", '-Goverlap=prism', '-Goverlap_scaling=-10', "-T$format", '-o', "${inputfile}.${layout}.$format", inputfile ] println args final Process execute = args.execute() execute.waitFor() println execute.exitValue() } }
这是一个图库,其中包含一些创建和缩小的图像的示例,以使其对网络友好。 我使用此数据生成的完整尺寸图,单个PNG文件的最大尺寸为300MB。 SVG格式占用的空间大大减少,但仍超过10MB。 我也很难找到SVG格式的查看器,该查看器是:a)能够以可导航的方式显示大图,并且b)不会由于内存使用而使我的浏览器崩溃。
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
只是为了好玩
最初,我原本打算将此功能作为使用Gaelyk的应用程序发布在Google App Engine上,但是由于API限制使其适合每小时处理几乎一个请求,并且可能使我在GitHub上遇到麻烦,因此我最终说到一点。 但是一路上,我开发了一个非常简单的页面,该页面将为特定用户加载所有公开可用的Gist,并将其显示在表格中。 这是一个非常干净的示例,说明如何使用GAE + Gaelyk编写快速又肮脏的应用程序并使之公开可用。 这涉及将gradle-gaelyk插件与gradle-gae-plugin结合使用来设置基础架构,并使用Gradle来构建,测试和将应用程序部署到Web上-总共花费了一个小时的时间。 尝试使用此链接加载我所有公开可用的Gists-如果您想查看其他人,请替换username参数。 请稍等一下,因为GAE会在短时间内未请求该应用程序,因此它将取消部署该应用程序,因此第一次调用可能需要花费几秒钟的时间。
http://publicgists.appspot.com/gist?username=kellyrob99
这是Groovlet实现,该实现加载数据,然后转发到模板页面。
1
2
3
4
5
6
7def username = request.getParameter('username') ?: 'kellyrob99' def text = "https://gist.github.com/api/v1/json/gists/$username".toURL().text log.info text request.setAttribute('rawJSON', text) request.setAttribute('username', username) forward '/WEB-INF/pages/gist.gtpl'
以及随附的模板页面,该页面呈现API请求的简单表格视图。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<% include '/WEB-INF/includes/header.gtpl' %> <% import groovy.json.JsonSlurper %> <% def gistMap = new JsonSlurper().parseText(request['rawJSON']) %> <h1>Public Gists for username : ${request['username']} </h1> <p> <table class = "gridtable"> <th>Description</th> <th>Web page</th> <th>Repo</th> <th>Owner</th> <th>Files</th> <th>Created at</th> <% gistMap.gists.each { data -> def repo = data.repo %> <tr> <td>${data.description ?: ''}</td> <td> <a href="https://gist.github.com/${repo}">${repo}</a> </td> <td> <a href= "git://gist.github.com/${repo}.git">${repo}</a> </td> <td>{data.owner}</td> <td>${data.files}</td> <td>${data.created_at}</td> </tr> <% } %> </table> </p> <% include '/WEB-INF/includes/footer.gtpl' %>
参考: The Kaptain on…内容博客中的JCG合作伙伴 Kelly Robinson的GitHub Social Graphs with Groovy和GraphViz 。
翻译自: https://www.javacodegeeks.com/2012/06/github-social-graphs-with-groovy-and.html
groovy有什么用
最后
以上就是陶醉路人最近收集整理的关于groovy有什么用_带有Groovy和GraphViz的GitHub社交图的全部内容,更多相关groovy有什么用_带有Groovy和GraphViz内容请搜索靠谱客的其他文章。
发表评论 取消回复