diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..162b38e --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*.class +.classpath +.project +.settings + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# email file +out_email.html + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +/target/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e06d208 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md index 7541d4b..5a16d2d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,107 @@ # tools.github -Some utilities for working with github +A repo that allows running automated Actions on a subset of github repos via Filters + +# To Build +From top level run mvn clean install + +# Usage + +`java -jar tools.github-1.0.0-SNAPSHOT.jar -u USERNAME -f FILTER -a ACTION` + +Using the jar generated in target (tools.github-1.0.0-SNAPSHOT.jar) + +Place your credentials in ~/.github as + +``` +login= +password= +``` + +usage: [-a ] [-f ] [-if ] [-list ] [-of ] [-u ] + -a,--action action to do when found + -f,--filter filter to apply in search + -list,--help list (actions) or (options) + -u,--user user/organization to apply filter to + +-f is a filter +-a is an action + +example use cases + +clone all private repos +`java -jar tools.github-develop-SNAPSHOT.jar -u dpwspoon -a clone\(myRepos\) -f private` + +delete specific repo +`java -jar tools.github-develop-SNAPSHOT.jar -u dpwspoon -a delete -f byName\(gateway.bridge\)` + +find repos that have .travis.yml files +`java -jar target/tools.github-develop-SNAPSHOT.jar -u dpwspoon -f hasFile\(/,.travis.yml\) -a print-names` + +finds issues that have been updated in last day +`java -jar target/tools.github-develop-SNAPSHOT.jar -u dpwspoon -f all -a list-issues-recently-updated` + +finds issues labeled as `bug` that have not yet been assigned +`java -jar target/tools.github-develop-SNAPSHOT.jar -u dpwspoon -f all -a list-bugs-not-assigned` + +finds issues with milestone `S10 - 15` +`java -jar target/tools.github-develop-SNAPSHOT.jar -u dpwspoon -f all -a list-issues-by-milestone\(S10\ -\ 15\)` + +# List of Filters +``` +all Matches all repos +private Matches private repos +hasFile(path,file-name) filters repositories that has file +public Matches public repos +byName(name, name2 etc) gets a repo matching the name +``` +# List of Actions +``` +print-names: prints the name of the repo +addTeam(organization,team): Adds a team from an organization to a repository +clone(directory): clones the repo to a directory/name-of-repo +delete: deletes the repo +listOpenPullRequests lists open pull requests +listOpenIssues lists open issues (NOT IMPLEMENTED) +list-bugs-not-assigned lists bugs not assigned +list-issues-recently-closed lists issues recently closed (1 day) +list-issues-recently-updated lists issues recently closed (1 day) +list-issues-by-milestone(milestone) lists issues matching argument milestone +``` + +# List of Issue Filters +Note: These are not accesible from the command line, rather actions or other apps can build with them. +``` +ALL matches all issues +OPEN matches open issues +CLOSED matches closed issues +CLOSED_WITHIN_DAYS(numberDays) matches issues closed within argument days +UPDATED_WITHIN_DAYS(numberDays) matches issues updated within argument days +HAS_LABEL(label) matches issues containing argument label +HAS_ASSIGNEE(login) login of user, null argument returns issues with no assignee +BY_MILESTONE(milestone) filters by milestone +BY_NAME gets issue matching the name +``` + +# Apps +### DailyEmail.java + +``` +cd target && java -cp tools.github-develop-SNAPSHOT.jar org.kaazing.github.apps.DailyEmail && cd .. +``` + +Compiles and outputs both plain text (command line) and html (out_email.html) of users and key issues. +Currently the app is programmed to search for the following by user: + + 1. Open Pull Requests + 2. Issues Updated Recently (within a day) + 3. Backlog Issues (issues not updated within a day but have a milestone) + 4. Issues Closed Recently (within a day) + +The `main` takes one optional argument for the login of a user. If a string is passed, the program will +only output the issues (above) for no assignee and for the argument user. If no argument is passed, the +program will output no assignee and then all users. + +Future: Perhaps configure utility to send out an email given the html and the plain text output. + +# NOTES +Mileage may vary. Most of these have not been fully tested yet. Also, the lib is best used to write your own Java Program diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6fec8c9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,142 @@ + + 4.0.0 + + + org.kaazing + community + 2.17 + + + tools.github + develop-SNAPSHOT + jar + tools.github + + https://github.com/kaazing/github.utils + Kaazing Github Utils + + + https://github.com/kaazing/gateway + scm:git:https://github.com/kaazing/gateway.git + + + + UTF-8 + 3.3.1.201403241930-r + 0.0.7 + 1.8 + 1.8 + 1.8 + 1.8 + + true + + + + + org.kohsuke + github-api + 1.69 + + + junit + junit + 4.12 + test + + + org.eclipse.jgit + org.eclipse.jgit.console + ${jgit.version} + + + com.jcraft + jsch + + + + + org.eclipse.jgit + org.eclipse.jgit + ${jgit.version} + + + com.jcraft + jsch + + + + + com.jcraft + jsch.agentproxy.jsch + ${jsch.agent.version} + + + com.jcraft + jsch.agentproxy.usocket-jna + ${jsch.agent.version} + + + com.jcraft + jsch.agentproxy.sshagent + ${jsch.agent.version} + + + org.apache.servicemix.bundles + org.apache.servicemix.bundles.jsch + 0.1.49_1 + + + commons-cli + commons-cli + 1.2 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + false + + + org.kaazing.github.Crawler + + + + org/kaazing/github/utils/ + + ${project.version} + + + + + + + + make-assembly + package + + single + + + + + + + diff --git a/src/main/java/org/kaazing/github/Action.java b/src/main/java/org/kaazing/github/Action.java new file mode 100644 index 0000000..9296be7 --- /dev/null +++ b/src/main/java/org/kaazing/github/Action.java @@ -0,0 +1,398 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github; + +import static org.kaazing.github.FilterIssues.BY_MILESTONE; +import static org.kaazing.github.FilterIssues.CLOSED_WITHIN_DAYS; +import static org.kaazing.github.FilterIssues.HAS_ASSIGNEE; +import static org.kaazing.github.FilterIssues.HAS_LABEL; +import static org.kaazing.github.FilterIssues.UPDATED_WITHIN_DAYS; +import static org.kohsuke.github.GHIssueState.CLOSED; +import static org.kohsuke.github.GHIssueState.OPEN; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.kohsuke.github.GHIssue; +import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHTeam; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.PagedIterable; +import org.kohsuke.github.PagedIterator; + +public abstract class Action extends SubCommand implements Consumer { + + public static Map getActionsByName() { + Map actionsByName = new HashMap<>(); + actionsByName.put(PRINT_NAMES.getName(), PRINT_NAMES); + actionsByName.put(CLONE.getName(), CLONE); + actionsByName.put(ADD_TEAM_TO_REPO.getName(), ADD_TEAM_TO_REPO); + actionsByName.put(DELETE.getName(), DELETE); + actionsByName.put(LIST_ISSUES_CLOSED_WITHIN_DAY.getName(), LIST_ISSUES_CLOSED_WITHIN_DAY); + actionsByName.put(LIST_ISSUES_UPDATED_WITHIN_DAY.getName(), LIST_ISSUES_UPDATED_WITHIN_DAY); + actionsByName.put(LIST_OPEN_PULL_REQUESTS.getName(), LIST_OPEN_PULL_REQUESTS); + actionsByName.put(LIST_BUGS_NOT_ASSIGNED.getName(), LIST_BUGS_NOT_ASSIGNED); + actionsByName.put(LIST_ISSUES_BY_MILESTONE.getName(), LIST_ISSUES_BY_MILESTONE); + return actionsByName; + } + + public static Action PRINT_NAMES = new Action() { + @Override + public void accept(GHRepository t) { + System.out.println(t.getFullName()); + } + + @Override + public String getName() { + return "print-names"; + } + + @Override + public String getDescription() { + return "prints the name of the repo"; + } + + @Override + public String listArgs() { + return ""; + } + }; + + public static Action LIST_OPEN_PULL_REQUESTS = new Action() { + + @Override + public void accept(GHRepository t) { + PagedIterable open = t.listPullRequests(GHIssueState.OPEN); + PagedIterator iter = open.iterator(); + while (iter.hasNext()) { + GHPullRequest next = iter.next(); + GHUser user = next.getAssignee(); + if (user != null) + System.out + .println(t.getName() + "#" + next.getNumber() + " -- " + next.getTitle() + " -- " + user.getLogin()); + else + System.out.println( + t.getName() + "#" + next.getNumber() + " -- " + next.getTitle() + " -- " + "NO ASSIGNED USER"); + } + } + + @Override + public String getName() { + return "listOpenPullRequests"; + } + + @Override + public String getDescription() { + return "lists open pull requests"; + } + + @Override + public String listArgs() { + return ""; + } + }; + + public static Action ADD_TEAM_TO_REPO = new Action() { + + @Override + public void accept(GHRepository r) { + String[] args = getArgs(); + String organization = args[0]; + String team = args[1]; + if (team == null || organization == null) { + throw new RuntimeException(this.getName() + " requires a non null arg"); + } + try { + GHOrganization org = GitHubBuilder.fromCredentials().build().getOrganization(organization); + GHTeam githubTeam = org.getTeamByName(team); + githubTeam.add(r); + System.out.println("Added: " + githubTeam.getName() + " to " + r.getName()); + } catch (IOException e1) { + throw new RuntimeException(e1); + } + } + + @Override + public String getName() { + return "addTeam"; + } + + @Override + public String getDescription() { + return "Adds a team from an organization to a repository"; + } + + @Override + public String listArgs() { + return "(organization,team)"; + } + }; + + public static Action CLONE = new Action() { + + UsernamePasswordCredentialsProvider credentialsProvider = new PropertyFileCredentialsProvider(); + + @Override + public void accept(GHRepository r) { + try { + String[] args = getArgs(); + String directoryName = args[0]; + if (directoryName == null) { + throw new RuntimeException(this.getName() + " requires a non null arg"); + } + + File directory = new File(directoryName); + if (!directory.exists()) { + directory.mkdirs(); + } + directory = new File(directory, r.getName()); + for (int trys = 0; true; trys++) { + try { + Git.cloneRepository().setCredentialsProvider(credentialsProvider).setDirectory(directory) + .setURI(r.getUrl().toString()).call(); + break; + } catch (TransportException e) { + // sporadic failure + Utils.deleteFolder(directory); + if (trys > 3) { + throw e; + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String getName() { + return "clone"; + } + + @Override + public String getDescription() { + return "clones the repo to a directory/name-of-repo"; + } + + @Override + public String listArgs() { + return "(directory)"; + } + }; + + public static Action DELETE = new Action() { + + @Override + public void accept(GHRepository t) { + try { + t.delete(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String listArgs() { + return ""; + } + + @Override + public String getName() { + return "delete"; + } + + @Override + public String getDescription() { + return "deletes the repo"; + } + }; + + public static Action LIST_ISSUES_CLOSED_WITHIN_DAY = new Action() { + + @Override + public void accept(GHRepository t) { + List closed = null; + try { + closed = t.getIssues(CLOSED); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Iterator iter = closed.iterator(); + while (iter.hasNext()) { + GHIssue next = iter.next(); + FilterIssues filter = CLOSED_WITHIN_DAYS; + String[] args = {"1"}; + filter.setArgs(args); + if (filter.test(next)) + System.out.println(t.getFullName() + "#" + next.getNumber() + " -- " + next.getTitle()); + } + } + + @Override + public String getName() { + return "list-issues-recently-closed"; + } + + @Override + public String getDescription() { + return "get all recently closed issues"; + } + + @Override + public String listArgs() { + return ""; + } + + }; + + public static Action LIST_ISSUES_UPDATED_WITHIN_DAY = new Action() { + + @Override + public void accept(GHRepository t) { + List closed = null; + try { + closed = t.getIssues(OPEN); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Iterator iter = closed.iterator(); + while (iter.hasNext()) { + GHIssue next = iter.next(); + FilterIssues filter = UPDATED_WITHIN_DAYS; + String[] args = {"1"}; + filter.setArgs(args); + if (filter.test(next)) + System.out.println(t.getFullName() + "#" + next.getNumber() + " -- " + next.getTitle()); + } + } + + @Override + public String getName() { + // TODO Auto-generated method stub + return "list-issues-recently-updated"; + } + + @Override + public String getDescription() { + // TODO Auto-generated method stub + return "get all recently updated issues"; + } + + @Override + public String listArgs() { + return ""; + } + + }; + + public static Action LIST_BUGS_NOT_ASSIGNED = new Action() { + + @Override + public void accept(GHRepository t) { + List closed = null; + try { + closed = t.getIssues(OPEN); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Iterator iter = closed.iterator(); + while (iter.hasNext()) { + GHIssue next = iter.next(); + FilterIssues filters = HAS_LABEL; + String[] args = {"bug"}; + filters.setArgs(args); + FilterIssues filter = HAS_ASSIGNEE; + filters.and(filter); + if (filters.test(next)) + System.out.println(t.getFullName() + "#" + next.getNumber() + " -- " + next.getTitle()); + } + } + + @Override + public String getName() { + return "list-bugs-not-assigned"; + } + + @Override + public String getDescription() { + return "get all bugs not assigned"; + } + + @Override + public String listArgs() { + return ""; + } + + }; + + public static Action LIST_ISSUES_BY_MILESTONE = new Action() { + + @Override + public void accept(GHRepository t) { + + List closed = null; + try { + closed = t.getIssues(OPEN); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Iterator iter = closed.iterator(); + while (iter.hasNext()) { + GHIssue next = iter.next(); + FilterIssues filter = BY_MILESTONE; + filter.setArgs(getArgs()); + if (filter.test(next)) + System.out.println(t.getFullName() + "#" + next.getNumber() + " -- " + next.getTitle()); + } + } + + @Override + public String getName() { + // TODO Auto-generated method stub + return "list-issues-by-milestone"; + } + + @Override + public String getDescription() { + // TODO Auto-generated method stub + return "get all recently closed issues"; + } + + @Override + public String listArgs() { + // TODO Auto-generated method stub + return "(milestone)"; + } + + }; + +} diff --git a/src/main/java/org/kaazing/github/Crawler.java b/src/main/java/org/kaazing/github/Crawler.java new file mode 100644 index 0000000..75770aa --- /dev/null +++ b/src/main/java/org/kaazing/github/Crawler.java @@ -0,0 +1,169 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github; + +import java.io.IOException; +import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.Parser; +import org.apache.commons.cli.PosixParser; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.PagedIterable; +import org.kohsuke.github.PagedIterator; + +public class Crawler { + + private final static String CMD_OPTION_FILTER = "filter"; + private final static String CMD_OPTION_ACTION = "action"; + private final static String CMD_OPTION_USER = "user"; + private final static String CMD_OPTION_LIST = "help"; + private final String user; + private final Predicate filters; + private final Action actions; + public static final Pattern ARGS_PATTERN = Pattern.compile("(?.*)\\((?.*)\\)"); + + public Crawler(String user, Predicate filters, Action actions) { + this.user = user; + this.filters = filters; + this.actions = actions; + } + + public static void main(String... args) throws Exception { + Options options = + new Options().addOption("f", CMD_OPTION_FILTER, true, "filter to apply in search") + .addOption("a", CMD_OPTION_ACTION, true, "action to do when found") + .addOption("u", CMD_OPTION_USER, true, "user/organization to apply filter to") + .addOption("list", CMD_OPTION_LIST, true, "list (actions) or (options)"); + + try { + Parser parser = new PosixParser(); + CommandLine cmd = parser.parse(options, args); + + Map filtersByName = Filter.getFiltersByName(); + Map actionsByName = Action.getActionsByName(); + // check if list command + String list = cmd.getOptionValue(CMD_OPTION_LIST, null); + if (list != null) { + if (list.equalsIgnoreCase("actions")) { + for (Action action : actionsByName.values()) { + System.out.println(action.getName() + action.listArgs() + "\t\t" + action.getDescription()); + } + } else if (list.equalsIgnoreCase("filters")) { + for (Filter filter : filtersByName.values()) { + System.out.println(filter.getName() + filter.listArgs() + "\t\t" + filter.getDescription()); + } + } else { + throw new IllegalArgumentException("list command accepts \"filters\" or \"actions\""); + } + } else { + // get filters + String nameOfFilters = cmd.getOptionValue(CMD_OPTION_FILTER, ""); + Predicate filters = null; + for (String nameOfFilter : nameOfFilters.split(";")) { + Matcher matcher = ARGS_PATTERN.matcher(nameOfFilter); + boolean matches = matcher.matches(); + if(matches){ + nameOfFilter = matcher.group(1); + } + Filter filter = filtersByName.get(nameOfFilter.trim()); + if (filter == null) { + throw new IllegalArgumentException("could not find filter: " + nameOfFilter); + } + if(matches){ + filter.setArgs(matcher.group(2).split(",")); + } + if (filters == null) { + filters = filter; + } else { + filters.and(filter); + } + } + + // get actions + String nameOfActions = cmd.getOptionValue(CMD_OPTION_ACTION, ""); + Action actions = null; + for (String nameOfAction : nameOfActions.split(";")) { + Matcher matcher = ARGS_PATTERN.matcher(nameOfAction); + boolean matches = matcher.matches(); + if(matches){ + nameOfAction = matcher.group(1); + } + Action action = actionsByName.get(nameOfAction.trim()); + if (action == null) { + throw new IllegalArgumentException("could not find action: " + action); + } + if(matches){ + action.setArgs(matcher.group(2).split(",")); + } + if (actions == null) { + actions = action; + } else { + actions.andThen(action); + } + } + + // get user + String user = cmd.getOptionValue(CMD_OPTION_USER, null); + if (user == null) { + throw new IllegalArgumentException("Cannot accept a null user"); + } + + // see if help command + cmd.getOptionValue(CMD_OPTION_ACTION, null); + + // init crawler + Crawler crawler = new Crawler(user, filters, actions); + + // crawl user's repositories + crawler.run(); + } + } catch (ParseException ex) { + HelpFormatter helpFormatter = new HelpFormatter(); + helpFormatter.printHelp(Crawler.class.getSimpleName(), options, true); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + HelpFormatter helpFormatter = new HelpFormatter(); + helpFormatter.printHelp(Crawler.class.getSimpleName(), options, true); + } + } + + public void run() throws IOException { + String myUserName = GitHubBuilder.fromCredentials().build().getMyself().getLogin(); + PagedIterable repositories; + if (myUserName.equalsIgnoreCase(user)) { + repositories = GitHubBuilder.fromCredentials().build().getMyself().listRepositories(); + } else { + repositories = GitHubBuilder.fromCredentials().build().getOrganization(user).listRepositories(); + } + + // apply filters and actions + PagedIterator iter = repositories.iterator(); + while (iter.hasNext()) { + GHRepository repo = iter.next(); + if (filters.test(repo)) { + actions.accept(repo); + } + } + } +} diff --git a/src/main/java/org/kaazing/github/Filter.java b/src/main/java/org/kaazing/github/Filter.java new file mode 100644 index 0000000..0a422ae --- /dev/null +++ b/src/main/java/org/kaazing/github/Filter.java @@ -0,0 +1,174 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHRepository; + +public abstract class Filter extends SubCommand implements Predicate { + + public static Map getFiltersByName() { + Map filtersByName = new HashMap<>(); + filtersByName.put(ALL.getName(), ALL); + filtersByName.put(PRIVATE.getName(), PRIVATE); + filtersByName.put(PUBLIC.getName(), PUBLIC); + filtersByName.put(HAS_FILE.getName(), HAS_FILE); + filtersByName.put(BY_NAME.getName(), BY_NAME); + return filtersByName; + } + + public static final Filter ALL = new Filter() { + @Override + public boolean test(GHRepository t) { + return true; + } + + @Override + public String getName() { + return "all"; + } + + @Override + public String getDescription() { + return "Matches all repos"; + } + + @Override + public String listArgs() { + return ""; + } + }; + + public static final Filter PRIVATE = new Filter() { + @Override + public boolean test(GHRepository t) { + return t.isPrivate(); + } + + @Override + public String getName() { + return "private"; + } + + @Override + public String getDescription() { + return "Matches private repos"; + } + + @Override + public String listArgs() { + return ""; + } + }; + + public static final Filter PUBLIC = new Filter() { + @Override + public boolean test(GHRepository t) { + return !t.isPrivate(); + } + + @Override + public String getName() { + return "public"; + } + + @Override + public String getDescription() { + return "Matches public repos"; + } + + @Override + public String listArgs() { + return ""; + } + }; + + public static final Filter HAS_FILE = new Filter() { + + @Override + public boolean test(GHRepository t) { + String[] args = getArgs(); + String path = args[0]; + String name = args[1]; + try { + for (GHContent content : t.getDirectoryContent(path)) { + if (name.equalsIgnoreCase(content.getName())) { + return true; + } + } + } catch (FileNotFoundException e) { + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + @Override + public String getName() { + return "hasFile"; + } + + @Override + public String getDescription() { + return "filters repositories that has files"; + } + + @Override + public String listArgs() { + return "(path,file-name)"; + } + + }; + + public static final Filter BY_NAME = new Filter() { + + @Override + public boolean test(GHRepository t) { + List names = Arrays.asList(getArgs()); + if(names.isEmpty()){ + throw new RuntimeException("Filter by name requires non null arg"); + } + if(names.contains(t.getName())){ + return true; + } + return false; + } + + @Override + public String listArgs() { + return "(name)"; + } + + @Override + public String getName() { + return "byName"; + } + + @Override + public String getDescription() { + return "gets a repo matching the name"; + } + }; + +} diff --git a/src/main/java/org/kaazing/github/FilterIssues.java b/src/main/java/org/kaazing/github/FilterIssues.java new file mode 100644 index 0000000..2dd514e --- /dev/null +++ b/src/main/java/org/kaazing/github/FilterIssues.java @@ -0,0 +1,328 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHIssue; +import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHLabel; +import org.kohsuke.github.GHMilestone; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHUser; + +public abstract class FilterIssues extends SubCommand implements Predicate { + + public static Map getFiltersByName() { + Map filtersByName = new HashMap<>(); + filtersByName.put(ALL.getName(), ALL); + filtersByName.put(BY_NAME.getName(), BY_NAME); + return filtersByName; + } + + public static final FilterIssues ALL = new FilterIssues() { + @Override + public boolean test(GHIssue t) { + return true; + } + + @Override + public String getName() { + return "all"; + } + + @Override + public String getDescription() { + return "Matches all repos"; + } + + @Override + public String listArgs() { + return ""; + } + }; + + public static final FilterIssues OPEN = new FilterIssues() { + @Override + public boolean test(GHIssue t) { + return t.getState() == GHIssueState.OPEN; + } + + @Override + public String getName() { + return "private"; + } + + @Override + public String getDescription() { + return "Matches private repos"; + } + + @Override + public String listArgs() { + return ""; + } + }; + + public static final FilterIssues CLOSED = new FilterIssues() { + @Override + public boolean test(GHIssue t) { + return t.getState() == GHIssueState.CLOSED; + } + + @Override + public String getName() { + return "public"; + } + + @Override + public String getDescription() { + return "Matches public repos"; + } + + @Override + public String listArgs() { + return ""; + } + }; + + public static final FilterIssues CLOSED_WITHIN_DAYS = new FilterIssues() { + @Override + public boolean test(GHIssue t) { + if (t.getState() == GHIssueState.OPEN) + return false; + + String[] args = getArgs(); + int days = Integer.parseInt(args[0]); + + Calendar dateClosed = new GregorianCalendar(); + dateClosed.setTime(t.getClosedAt()); + Calendar cutOffDate = new GregorianCalendar(); + cutOffDate.setTime(new Date()); + cutOffDate.add(Calendar.DAY_OF_MONTH, -days); + + if (dateClosed.after(cutOffDate)) + return true; + return false; + } + + @Override + public String getName() { + return "closed-within-days"; + } + + @Override + public String getDescription() { + return "Issues closed within argument days"; + } + + @Override + public String listArgs() { + return "(numberDays)"; + } + }; + + public static final FilterIssues UPDATED_WITHIN_DAYS = new FilterIssues() { + @Override + public boolean test(GHIssue t) { + String[] args = getArgs(); + int days = Integer.parseInt(args[0]); + + Calendar dateClosed = new GregorianCalendar(); + try { + dateClosed.setTime(t.getUpdatedAt()); + } catch(IOException e) { + System.out.println("This issue did not have updated: " + t.getTitle()); + return false; + } + Calendar cutOffDate = new GregorianCalendar(); + cutOffDate.setTime(new Date()); + cutOffDate.add(Calendar.DAY_OF_MONTH, -days); + cutOffDate.add(Calendar.HOUR_OF_DAY, -1); + + if (dateClosed.after(cutOffDate)) + return true; + return false; + } + + @Override + public String getName() { + return "updated-within-days"; + } + + @Override + public String getDescription() { + return "Issued updated within argument days"; + } + + @Override + public String listArgs() { + return "(numberDays)"; + } + }; + + public static final FilterIssues HAS_LABEL = new FilterIssues() { + @Override + public boolean test(GHIssue t) { + String[] args = getArgs(); + String targetLabel = args[0]; + + Collection labels = null; + try { + labels = t.getLabels(); + } catch(IOException e) { + return false; + } + + for (GHLabel label : labels) + if (label.getName().equals(targetLabel)) { + return true; + } + + return false; + } + + @Override + public String getName() { + return "has-label"; + } + + @Override + public String getDescription() { + return "filters issues containing argument label"; + } + + @Override + public String listArgs() { + return "(label)"; + } + }; + + public static final FilterIssues HAS_ASSIGNEE = new FilterIssues() { + @Override + public boolean test(GHIssue t) { + String[] args = getArgs(); + String assignee = null; + if (args.length > 0) + assignee = args[0]; + + + GHUser user = t.getAssignee(); + if (user == null) { + if (assignee == null) + return true; + } else if (user.getLogin() == assignee) { + return true; + } + + return false; + } + + @Override + public String getName() { + return "has-label"; + } + + @Override + public String getDescription() { + return "filters issues by assignee user login"; + } + + @Override + public String listArgs() { + return "(login) [no arguments for no assignee]"; + } + }; + + public static final FilterIssues BY_MILESTONE = new FilterIssues() { + @Override + public boolean test(GHIssue t) { + String[] args = getArgs(); + String milestone = args[0]; + + if (milestone == null) + throw new RuntimeException("milestone needs not null argument"); + + GHMilestone issueMilestone = t.getMilestone(); + + if (issueMilestone == null) { + return false; + } + + if (issueMilestone.getTitle().equals(milestone)) + return true; + + return false; + } + + @Override + public String getName() { + return "public"; + } + + @Override + public String getDescription() { + return "Matches public repos"; + } + + @Override + public String listArgs() { + return "(milestone)"; + } + }; + + public static final FilterIssues BY_NAME = new FilterIssues() { + + @Override + public boolean test(GHIssue t) { + List names = Arrays.asList(getArgs()); + if(names.isEmpty()){ + throw new RuntimeException("Filter by name requires non null arg"); + } + if(names.contains(t.getTitle())){ + return true; + } + return false; + } + + @Override + public String listArgs() { + return "(name)"; + } + + @Override + public String getName() { + return "byName"; + } + + @Override + public String getDescription() { + return "gets a repo matching the name"; + } + }; + +} diff --git a/src/main/java/org/kaazing/github/PropertyFileCredentialsProvider.java b/src/main/java/org/kaazing/github/PropertyFileCredentialsProvider.java new file mode 100644 index 0000000..2d82b04 --- /dev/null +++ b/src/main/java/org/kaazing/github/PropertyFileCredentialsProvider.java @@ -0,0 +1,56 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github; + +import java.io.File; +import java.io.FileInputStream; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; + +/** + * Shares the same property file ~/.github which is used by org.kohsuke:github-api + * + */ +public class PropertyFileCredentialsProvider extends UsernamePasswordCredentialsProvider { + private final static String USERNAME; + private final static String PASSWORD; + + static { + try { + File homeDir = new File(System.getProperty("user.home")); + File propertyFile = new File(homeDir, ".github"); + Properties props = new Properties(); + FileInputStream in = null; + try { + in = new FileInputStream(propertyFile); + props.load(in); + } finally { + IOUtils.closeQuietly(in); + } + USERNAME = props.getProperty("login"); + PASSWORD = props.getProperty("password"); + } catch (Exception e) { + throw new RuntimeException("Could not get credentials", e); + } + } + + public PropertyFileCredentialsProvider() { + super(USERNAME, PASSWORD); + } + +} diff --git a/src/main/java/org/kaazing/github/SubCommand.java b/src/main/java/org/kaazing/github/SubCommand.java new file mode 100644 index 0000000..2426a97 --- /dev/null +++ b/src/main/java/org/kaazing/github/SubCommand.java @@ -0,0 +1,54 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github; + +public abstract class SubCommand { + + private String[] args = new String[]{}; + + /** + * Name of the command as ran from the command line + * @return + */ + public abstract String getName(); + + /** + * Description of what the command does + * @return + */ + public abstract String getDescription(); + + public String[] getArgs() { + return args; + } + + /** + * Args passed into the command + * @param args + */ + public void setArgs(String[] args) { + for (int i = 0; i < args.length; i++) { + args[i] = args[i].trim(); + } + this.args = args; + } + + /** + * List args that are required + * @return + */ + public abstract String listArgs(); +} diff --git a/src/main/java/org/kaazing/github/Utils.java b/src/main/java/org/kaazing/github/Utils.java new file mode 100644 index 0000000..fe10415 --- /dev/null +++ b/src/main/java/org/kaazing/github/Utils.java @@ -0,0 +1,36 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github; + +import java.io.File; + +public final class Utils { + + public static void deleteFolder(File folder) { + File[] files = folder.listFiles(); + if (files != null) { + for (File f : files) { + if (f.isDirectory()) { + deleteFolder(f); + } else { + f.delete(); + } + } + } + folder.delete(); + } + +} diff --git a/src/main/java/org/kaazing/github/apps/DailyEmail.java b/src/main/java/org/kaazing/github/apps/DailyEmail.java new file mode 100644 index 0000000..1384a2d --- /dev/null +++ b/src/main/java/org/kaazing/github/apps/DailyEmail.java @@ -0,0 +1,380 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github.apps; + +import org.kaazing.github.*; +import org.kohsuke.github.*; + +import static org.kaazing.github.FilterIssues.CLOSED_WITHIN_DAYS; +import static org.kaazing.github.FilterIssues.HAS_LABEL; +import static org.kaazing.github.FilterIssues.UPDATED_WITHIN_DAYS; +import static org.kohsuke.github.GHIssueState.CLOSED; +import static org.kohsuke.github.GHIssueState.OPEN; + +import java.awt.Color; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; + +public class DailyEmail { + private String htmlText = ""; + private String plainText = ""; + private List users = new ArrayList(); + private List pullRequests = new ArrayList(); + private List updatedOpenIssues = new ArrayList(); + private List backlogOpenIssues = new ArrayList(); + private List newlyClosedIssues = new ArrayList(); + private List escalations = new ArrayList(); + // private List newlyFiledBugs = new ArrayList(); + + /** + * main gathers data, compiles plain & html output, prints and saved to file + * @param args + * @throws Exception + */ + public static void main(String... args) throws Exception { + DailyEmail email = new DailyEmail(); + + email.gatherData(); + if (args.length > 0) + email.compileMessage(args[0]); + else + email.compileMessage(null); + email.send(); + } + + /** + * gather all data + * currently download open pull requests, issues updated recently, + * backlog issues w/ milestones, issues closed, and escalations + * recently == one day + * + * @throws IOException + */ + private void gatherData() throws IOException { + + List repositories; + repositories = GitHub.connect().getOrganization("kaazing").listRepositories().asList(); + repositories.addAll(GitHub.connect().getOrganization("kaazing-private").listRepositories().asList()); + repositories.addAll(GitHub.connect().getOrganization("k3po").listRepositories().asList()); + + // Get all open pull requests, get open issues, get issues closed w/i day, get issues updated within day + for (GHRepository repo : repositories) { + List temp = repo.getIssues(OPEN); + for (GHIssue issue : temp) { + + if (issue.isPullRequest()) + pullRequests.add(issue); + else { + FilterIssues filter = HAS_LABEL; + String[] args = {"escalation"}; + filter.setArgs(args); + + if (filter.test(issue)) { + escalations.add(issue); + } else { + filter = UPDATED_WITHIN_DAYS; + String[] args2 = {"1"}; + filter.setArgs(args2); + if (filter.test(issue)) + updatedOpenIssues.add(issue); + else if (issue.getMilestone() != null) + backlogOpenIssues.add(issue); + } + + } + + } + + temp = repo.getIssues(CLOSED); + for (GHIssue issue : temp) { + + FilterIssues filter = CLOSED_WITHIN_DAYS; + String[] args = {"1"}; + filter.setArgs(args); + if (!issue.isPullRequest() && filter.test(issue)) + newlyClosedIssues.add(issue); + } + } + + // gather and sort users with activity + for (GHIssue issue : pullRequests) { + GHUser user = issue.getAssignee(); + if (user != null && !users.contains(user)) + users.add(user); + } + + for (GHIssue issue : updatedOpenIssues) { + GHUser user = issue.getAssignee(); + if (user != null && !users.contains(user)) + users.add(user); + } + for (GHIssue issue : backlogOpenIssues) { + GHUser user = issue.getAssignee(); + if (user != null && !users.contains(user)) + users.add(user); + } + for (GHIssue issue : newlyClosedIssues) { + GHUser user = issue.getAssignee(); + if (user != null && !users.contains(user)) + users.add(user); + } + + Collections.sort(users, new Comparator() { + + @Override + public int compare(GHUser o1, GHUser o2) { + return o1.getLogin().compareTo(o2.getLogin()); + } + }); + } + + /** + * Compiles text output on param forUserLogin + * null argument will compile output for ALL Users + * string argument will compiles output for NO ASSIGNEE and for argument user + * + * @param forUserLogin + * @throws IOException + */ + private void compileMessage(String forUserLogin) throws IOException { + // Header + + plainText += getEscalationsText(); + + plainText += getNoAssigneeText(); + + for (GHUser user : users) { + if (forUserLogin != null) { + if (user.getLogin().equals(forUserLogin)) + plainText += getUserText(user); + } else { + plainText += getUserText(user); + } + } + + } + + /** + * returns plain text for escalation and adds html text to html output + * @return String + */ + private String getEscalationsText() { + String escalationText = "\nESCALATIONS:\n"; + htmlText += "\n

Escalations

\n"; + + boolean addedSomething = false; + + htmlText += "
    \n"; + for (GHIssue issue : escalations) { + escalationText += "\t\t" + plainTextForIssue(issue) + "\n"; + htmlText += "\t
  • " + htmlTextForIssue(issue) + "
  • \n"; + addedSomething = true; + } + htmlText += "
\n"; + + if (addedSomething) + return escalationText; + return ""; + + } + + /** + * returns plain text for user and adds html text to html output + * @return String + */ + private String getUserText(GHUser user) { + String userText = "\n" + user.getLogin() + ":\n"; + htmlText += "\n

" + user.getLogin() + "

\n"; + + boolean addedSomething = false; + + userText += "\n\tOPEN PULL REQUESTS:\n"; + htmlText += "
    \n\t
  • Open Pull Requests\n\t\t
      \n"; + for (GHIssue pull : pullRequests) { + if (user.equals(pull.getAssignee())) { + userText += "\t\t" + plainTextForIssue(pull) + " -- submitted by: " + pull.getUser().getLogin() + "\n"; + htmlText += "\t\t\t
    • " + htmlTextForIssue(pull) + " submitted by: " + pull.getUser().getLogin() + + "
    • \n"; + addedSomething = true; + } + } + htmlText += "\t\t
    \n\t
  • \n"; + + userText += "\n\tISSUES UPDATED RECENTLY:\n"; + htmlText += "\t
  • Issues Updated Recently\n\t\t
      \n"; + for (GHIssue issue : updatedOpenIssues) { + if (user.equals(issue.getAssignee())) { + userText += "\t\t" + plainTextForIssue(issue) + "\n"; + htmlText += "\t\t\t
    • " + htmlTextForIssue(issue) + "
    • \n"; + addedSomething = true; + } + } + htmlText += "\t\t
    \n\t
  • \n"; + + userText += "\n\tBACKLOG ISSUES:\n"; + htmlText += "\t
  • Backlog Issues\n\t\t
      \n"; + for (GHIssue issue : backlogOpenIssues) { + if (user.equals(issue.getAssignee())) { + userText += "\t\t" + plainTextForIssue(issue) + "\n"; + htmlText += "\t\t\t
    • " + htmlTextForIssue(issue) + "
    • \n"; + addedSomething = true; + } + } + htmlText += "\t\t
    \n\t
  • \n"; + + userText += "\n\tISSUES CLOSED RECENTLY:\n"; + htmlText += "\t
  • Issues Closed Recently\n\t\t
      \n"; + for (GHIssue issue : newlyClosedIssues) { + if (user.equals(issue.getAssignee())) { + userText += "\t\t" + plainTextForIssue(issue) + "\n"; + htmlText += "\t\t\t
    • " + htmlTextForIssue(issue) + "
    • \n"; + addedSomething = true; + } + } + htmlText += "\t\t
    \n\t
  • \n
\n"; + + if (addedSomething) + return userText; + return ""; + } + + /** + * returns plain text for issues with no assignee and adds html text to html output + * @return String + */ + private String getNoAssigneeText() { + String userText; + userText = "\nNO ASSIGNEE:\n"; + htmlText += "\n

NO ASSIGNEE

\n"; + + boolean addedSomething = false; + + userText += "\n\tOPEN PULL REQUESTS:\n"; + htmlText += "
    \n\t
  • Open Pull Requests\n\t\t
      \n"; + for (GHIssue pull : pullRequests) { + if (null == pull.getAssignee()) { + userText += "\t\t" + plainTextForIssue(pull) + "\n"; + htmlText += "\t\t\t
    • " + htmlTextForIssue(pull) + " submitted by: " + pull.getUser().getLogin() + + "
    • \n"; + addedSomething = true; + } + } + htmlText += "\t\t
    \n\t
  • \n"; + + userText += "\n\tISSUES UPDATED RECENTLY:\n"; + htmlText += "\t
  • Issues Updated Recently\n\t\t
      \n"; + for (GHIssue issue : updatedOpenIssues) { + if (null == issue.getAssignee()) { + userText += "\t\t" + plainTextForIssue(issue) + "\n"; + htmlText += "\t\t\t
    • " + htmlTextForIssue(issue) + "
    • \n"; + addedSomething = true; + } + } + htmlText += "\t\t
    \n\t
  • \n"; + + userText += "\n\tISSUES CLOSED RECENTLY:\n"; + htmlText += "\t
  • Issues Closed Recently\n\t\t
      \n"; + for (GHIssue issue : newlyClosedIssues) { + if (null == issue.getAssignee()) { + userText += "\t\t" + plainTextForIssue(issue) + "\n"; + htmlText += "\t\t\t
    • " + htmlTextForIssue(issue) + "
    • \n"; + addedSomething = true; + } + } + htmlText += "\t\t
    \n\t
  • \n
\n"; + + if (addedSomething) + return userText; + return ""; + } + + /** + * returns html text for GHIssue + * @return String + */ + private String htmlTextForIssue(GHIssue issue) { + String html = ""; + html += ""; + html += "" + issue.getRepository().getName() + + ""; + html += " #" + issue.getNumber() + " " + issue.getTitle() + " "; + try { + for (GHLabel label : issue.getLabels()) { + html += "" + + label.getName() + " "; + } + } catch (IOException e) { + + } + GHMilestone milestone = issue.getMilestone(); + if (milestone != null) + html += " " + milestone.getTitle() + ""; + + html += ""; + return html; + } + + /** + * returns plain text for GHIssue + * @return String + */ private String plainTextForIssue(GHIssue issue) { + String text = issue.getRepository().getName() + "#" + issue.getNumber() + " -- " + issue.getTitle(); + try { + for (GHLabel label : issue.getLabels()) { + text += " / " + label.getName(); + } + } catch (IOException e) { + + } + return text; + } + + /** + * Prints plain text to System.out and saves html output to out_email.html + * @throws FileNotFoundException + */ + private void send() throws FileNotFoundException { + System.out.println(plainText); + System.out.println("done\n\n\n"); + + PrintStream out = new PrintStream("out_email.html"); + out.println(htmlText); + out.close(); + } + + /** + * Evaluates brightness of label color and returns correct font color, i.e. black or white + * @param hex + * @return String + */ + private String fontColorForBackground(String hex) { + // convert hex string to int + int rgb = Integer.parseInt(hex, 16); + + Color c = new Color(rgb); + + double brightness = 0.299 * c.getRed() + 0.587 * c.getGreen() + 0.114 * c.getBlue(); + + if (brightness > 200) { + return "black"; + } else { + return "white"; + } + } +} diff --git a/src/main/java/org/kaazing/github/apps/DeleteRepos.java b/src/main/java/org/kaazing/github/apps/DeleteRepos.java new file mode 100644 index 0000000..7c7df1f --- /dev/null +++ b/src/main/java/org/kaazing/github/apps/DeleteRepos.java @@ -0,0 +1,32 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github.apps; + +import org.kaazing.github.Action; +import org.kaazing.github.Crawler; +import org.kaazing.github.Filter; + +public class DeleteRepos { + + public static void main(String... args) throws Exception { + String user = "put user or org here"; + Filter filter = Filter.BY_NAME; + filter.setArgs(new String[]{"LIST", "REPOS", "here"}); + Crawler crawler = new Crawler(user, filter, Action.DELETE); + crawler.run(); + System.out.println("Done!"); + } +} diff --git a/src/test/java/org/kaazing/github/CrawlerTest.java b/src/test/java/org/kaazing/github/CrawlerTest.java new file mode 100644 index 0000000..7138f40 --- /dev/null +++ b/src/test/java/org/kaazing/github/CrawlerTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 2007-2015, Kaazing Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kaazing.github; + +import java.util.regex.Matcher; + +import org.junit.Assert; +import org.junit.Test; +import org.kaazing.github.Crawler; + +public class CrawlerTest { + + @Test + public void testArgsPattern(){ + Matcher matcher = Crawler.ARGS_PATTERN.matcher("has-file(/base/path/,file-name)"); + Assert.assertTrue(matcher.matches()); + Assert.assertTrue("has-file".equals(matcher.group(1))); + Assert.assertTrue("/base/path/,file-name".equals(matcher.group(2))); + } + + +}