├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ └── ci.yml ├── project ├── build.properties ├── plugin.sbt └── Build.scala ├── version.sbt ├── .gitignore ├── src ├── main │ └── scala │ │ ├── Interpreter.scala │ │ ├── PublicKey.scala │ │ ├── Gitignore.scala │ │ ├── Email.scala │ │ ├── Trees.scala │ │ ├── Ref.scala │ │ ├── SearchIssue.scala │ │ ├── SearchRepoSort.scala │ │ ├── Download.scala │ │ ├── Blob.scala │ │ ├── Branch.scala │ │ ├── Tag.scala │ │ ├── Comment.scala │ │ ├── package.scala │ │ ├── RepoEvent.scala │ │ ├── Tree.scala │ │ ├── Contents.scala │ │ ├── Gists.scala │ │ ├── SearchCode.scala │ │ ├── User.scala │ │ ├── Repo.scala │ │ ├── Pull.scala │ │ ├── Commit.scala │ │ ├── Gist.scala │ │ ├── Issues.scala │ │ ├── Command.scala │ │ └── Github.scala └── test │ └── scala │ └── Main.scala └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @xuwei-k 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.9 2 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.6.1-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | project/target 3 | project/project/target 4 | *.class 5 | *.jar 6 | *.swp 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /src/main/scala/Interpreter.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | import httpz.Action 4 | import scalaz.~> 5 | 6 | private[ghscala] object Interpreter extends (Command ~> Action) { 7 | override def apply[A](fa: Command[A]) = fa.action 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/PublicKey.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class PublicKey( 4 | id: Long, key: String 5 | ) extends JsonToString[PublicKey] 6 | 7 | object PublicKey { 8 | implicit val publicKeyCodecJson: CodecJson[PublicKey] = 9 | CodecJson.casecodec2(apply, unapply)( 10 | "id", "key" 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/Gitignore.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Gitignore( 4 | name: String, source: String 5 | ) extends JsonToString[Gitignore] 6 | 7 | object Gitignore { 8 | implicit val gitignoreCodecJson: CodecJson[Gitignore] = 9 | CodecJson.casecodec2(apply, unapply)( 10 | "name", "source" 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/Email.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Email ( 4 | email: String, primary: Boolean, verified: Boolean 5 | ) extends JsonToString[Email] 6 | 7 | object Email { 8 | implicit val emailCodecJson: CodecJson[Email] = 9 | CodecJson.casecodec3(apply, unapply)( 10 | "email", "primary", "verified" 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/Trees.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | object Trees { 4 | 5 | implicit val treesCodecJson: CodecJson[Trees] = 6 | CodecJson.casecodec3(apply, unapply)( 7 | "sha", "url", "tree" 8 | ) 9 | 10 | } 11 | 12 | final case class Trees( 13 | sha: String, url: String, tree: List[Tree] 14 | ) extends JsonToString[Trees] 15 | 16 | -------------------------------------------------------------------------------- /src/main/scala/Ref.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | case class Ref( 4 | ref :String, 5 | url :String, 6 | `object` :GitObj 7 | ){ 8 | lazy val name:String = ref.split('/').last 9 | lazy val isTag:Boolean = ref.split('/')(1) == "tags" 10 | lazy val isBranch:Boolean = ! isTag 11 | } 12 | 13 | case class GitObj(`type`:String,sha:String,url:String) 14 | 15 | -------------------------------------------------------------------------------- /src/main/scala/SearchIssue.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class SearchIssues( 4 | total_count :Long, 5 | items :List[Issue] 6 | ) extends JsonToString[SearchIssues] 7 | 8 | object SearchIssues { 9 | implicit val searchIssuesCodecJson: CodecJson[SearchIssues] = 10 | CodecJson.casecodec2(apply, unapply)( 11 | "total_count", "items" 12 | ) 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/scala/SearchRepoSort.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | sealed abstract class SearchRepoSort( 4 | private[ghscala] val name: Option[String] 5 | ) 6 | 7 | object SearchRepoSort { 8 | object Default extends SearchRepoSort(None) 9 | object Forks extends SearchRepoSort(Some("forks")) 10 | object Stars extends SearchRepoSort(Some("stars")) 11 | object Updated extends SearchRepoSort(Some("updated")) 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/main/scala/Download.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | // TODO deprecate 4 | // http://developer.github.com/v3/repos/downloads/ 5 | case class Download( 6 | content_type :Option[String], 7 | description :String, 8 | created_at :DateTime, 9 | html_url :String, 10 | url :String, 11 | size :Long, 12 | name :String, 13 | id :Long, 14 | download_count :Long 15 | ) 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 10 9 | strategy: 10 | fail-fast: false 11 | steps: 12 | - uses: actions/checkout@v5 13 | - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 14 | with: 15 | java-version: 8 16 | distribution: adopt 17 | - uses: coursier/cache-action@v6 18 | - uses: sbt/setup-sbt@v1 19 | - run: sbt -v "+ Test/compile" 20 | -------------------------------------------------------------------------------- /project/plugin.sbt: -------------------------------------------------------------------------------- 1 | scalacOptions ++= ( 2 | "-deprecation" :: 3 | "-unchecked" :: 4 | "-language:existentials" :: 5 | "-language:higherKinds" :: 6 | "-language:implicitConversions" :: 7 | Nil 8 | ) 9 | 10 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") 11 | 12 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.11") 13 | 14 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.5") 15 | 16 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") 17 | 18 | fullResolvers ~= {_.filterNot(_.name == "jcenter")} 19 | -------------------------------------------------------------------------------- /src/main/scala/Blob.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | import org.apache.commons.codec.binary.Base64 4 | 5 | final case class Blob( 6 | content :String, 7 | encoding :String, 8 | sha :String, 9 | size :Long, 10 | url :String 11 | ) extends JsonToString[Blob] { 12 | 13 | lazy val decoded:String = 14 | new String(Base64.decodeBase64(content)) 15 | 16 | } 17 | 18 | object Blob { 19 | 20 | implicit val blobCodecJson: CodecJson[Blob] = 21 | CodecJson.casecodec5(apply, unapply)( 22 | "content", "encoding", "sha", "size", "url" 23 | ) 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/main/scala/Branch.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Branch ( 4 | name :String, 5 | commit :Branch.Commit 6 | ) extends JsonToString[Branch] 7 | 8 | object Branch { 9 | implicit val branchCodecJson: CodecJson[Branch] = 10 | CodecJson.casecodec2(apply, unapply)( 11 | "name", "commit" 12 | ) 13 | 14 | final case class Commit( 15 | sha: String, url: String 16 | ) extends JsonToString[Commit] 17 | 18 | object Commit { 19 | implicit val commitCodecJson: CodecJson[Commit] = 20 | CodecJson.casecodec2(apply, unapply)( 21 | "sha", "url" 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/Tag.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Tag( 4 | name :String, 5 | zipball_url :String, 6 | tarball_url :String, 7 | commit :Tag.Commit 8 | ) extends JsonToString[Tag] 9 | 10 | 11 | object Tag { 12 | implicit val tagCodecJson: CodecJson[Tag] = 13 | CodecJson.casecodec4(apply, unapply)( 14 | "name", "zipball_url", "tarball_url", "commit" 15 | ) 16 | 17 | final case class Commit( 18 | sha: String, url: String 19 | ) extends JsonToString[Commit] 20 | 21 | object Commit { 22 | implicit val commitCodecJson: CodecJson[Commit] = 23 | CodecJson.casecodec2(apply, unapply)( 24 | "sha", "url" 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/Comment.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Comment( 4 | updated_at :String, 5 | id :Long, 6 | created_at :String, 7 | path :Option[String], 8 | body :String, 9 | html_url :String, 10 | commit_id :String, 11 | user :User, 12 | url :String, 13 | position :Option[Long], 14 | line :Option[Long] 15 | ) extends JsonToString[Comment] 16 | 17 | object Comment { 18 | 19 | implicit val commentCodecJson: CodecJson[Comment] = 20 | CodecJson.casecodec11(apply, unapply)( 21 | "updated_at", 22 | "id", 23 | "created_at", 24 | "path", 25 | "body", 26 | "html_url", 27 | "commit_id", 28 | "user", 29 | "url", 30 | "position", 31 | "line" 32 | ) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/package.scala: -------------------------------------------------------------------------------- 1 | import argonaut.{EncodeJson, DecodeJson} 2 | import httpz.Action 3 | import java.text.SimpleDateFormat 4 | import scalaz.~> 5 | 6 | package object ghscala{ 7 | 8 | type DateTime = org.joda.time.DateTime 9 | private[ghscala] type JsonToString[A <: httpz.JsonToString[A]] = 10 | httpz.JsonToString[A] 11 | 12 | type CodecJson[A] = argonaut.CodecJson[A] 13 | val CodecJson = argonaut.CodecJson 14 | 15 | implicit val datetimeCodecJson: CodecJson[DateTime] = 16 | CodecJson.derived( 17 | EncodeJson.jencode1(_.toString()), 18 | DecodeJson.optionDecoder({ 19 | _.string.map{ str => 20 | new DateTime((new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")).parse(str)) 21 | } 22 | },"DateTime") 23 | ) 24 | 25 | val interpreter: Command ~> Action = Interpreter 26 | } 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scala github api client 2 | 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.xuwei-k/ghscala_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.xuwei-k/ghscala_2.11) 4 | 5 | 6 | * https://docs.github.com 7 | 8 | 9 | - [Maven Central Repository Search](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.xuwei-k%22%20AND%20a%3A%22ghscala_2.13%22) 10 | - [Maven Central](https://repo1.maven.org/maven2/com/github/xuwei-k/) 11 | 12 | 13 | ### latest stable version 14 | 15 | ```scala 16 | libraryDependencies += "com.github.xuwei-k" %% "ghscala" % "0.6.0" 17 | ``` 18 | 19 | - [API Documentation](https://oss.sonatype.org/service/local/repositories/releases/archive/com/github/xuwei-k/ghscala_2.13/0.6.0/ghscala_2.13-0.6.0-javadoc.jar/!/index.html) 20 | 21 | 22 | ### for scalaz 7.1.x 23 | - 24 | -------------------------------------------------------------------------------- /src/main/scala/RepoEvent.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | import argonaut.{Json, JsonObject, DecodeJson} 4 | 5 | final case class RepoEvent( 6 | id :String, 7 | `type` :String, 8 | actor :User, 9 | payload :JsonObject, // TODO https://developer.github.com/v3/activity/events/types/ 10 | public :Boolean, 11 | created_at :DateTime 12 | ) extends JsonToString[RepoEvent] { 13 | def action: Option[String] = 14 | payload("action").flatMap(_.string) 15 | } 16 | 17 | object RepoEvent { 18 | 19 | private[this] implicit val jsonObjectCodecJson: CodecJson[JsonObject] = 20 | CodecJson( 21 | Json.jObject, 22 | DecodeJson.optionDecoder(_.obj, "JsonObject").decode 23 | ) 24 | 25 | implicit val repoEventCodecJson: CodecJson[RepoEvent] = 26 | CodecJson.casecodec6(apply, unapply)( 27 | "id", "type", "actor", "payload", "public", "created_at" 28 | ) 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/main/scala/Tree.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | object Tree { 4 | 5 | implicit val treeCodecJson: CodecJson[Tree] = 6 | CodecJson.casecodec6(apply, unapply)( 7 | "url", "sha", "type", "mode", "size", "path" 8 | ) 9 | 10 | } 11 | 12 | final case class Tree( 13 | url :String, 14 | sha :String, 15 | `type` :String, 16 | mode :String, 17 | size :Option[Long], 18 | path :String 19 | ){ 20 | lazy val getType:TreeType = `type` match { 21 | case "blob" => TreeType.Blob 22 | case "tree" => TreeType.Directory 23 | case _ => TreeType.Unknown(`type`) 24 | } 25 | } 26 | 27 | sealed abstract trait TreeType extends Any 28 | object TreeType{ 29 | case object Blob extends TreeType 30 | case object Directory extends TreeType 31 | case class Unknown(name:String) extends AnyVal with TreeType 32 | } 33 | 34 | case class TreeResponse( 35 | sha :String, 36 | tree :List[Tree], 37 | url :String 38 | ) 39 | 40 | -------------------------------------------------------------------------------- /src/main/scala/Contents.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | import org.apache.commons.codec.binary.Base64 4 | //import pamflet.PamfletDiscounter._ // TODO 5 | 6 | final case class Contents( 7 | sha :String, 8 | name :String, 9 | path :String, 10 | content :String, 11 | _links :Links, 12 | `type` :String, 13 | encoding :String, 14 | size :Long, 15 | url :String, 16 | html_url :String, 17 | git_url :String 18 | ) extends JsonToString[Contents] { 19 | 20 | lazy val decoded: String = 21 | new String(Base64.decodeBase64(content)) 22 | 23 | // lazy val html = toXHTML(knockoff(decoded)) 24 | } 25 | 26 | object Contents { 27 | 28 | implicit val contentsCodecJson: CodecJson[Contents] = 29 | CodecJson.casecodec11(apply, unapply)( 30 | "sha", "name", "path", "content", "_links", "type", "encoding", 31 | "size", "url", "html_url", "git_url" 32 | ) 33 | 34 | } 35 | 36 | 37 | final case class Links( 38 | self :String, 39 | git :String, 40 | html :String 41 | ) extends JsonToString[Links] 42 | 43 | object Links { 44 | implicit val linksCodecJson: CodecJson[Links] = 45 | CodecJson.casecodec3(apply, unapply)( 46 | "self", "git", "html" 47 | ) 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/main/scala/Gists.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Gists( 4 | files :Map[String, Gists.File], 5 | html_url :String, 6 | forks_url :String, 7 | comments :Long, 8 | created_at :String, 9 | id :String, 10 | owner :Option[User], 11 | commits_url :String, 12 | git_pull_url :String, 13 | public :Boolean, 14 | updated_at :String, 15 | comments_url :String, 16 | url :String, 17 | description :Option[String], 18 | git_push_url :String 19 | ) extends JsonToString[Gists] 20 | 21 | object Gists { 22 | 23 | final case class File( 24 | filename :String, 25 | language :Option[String], 26 | _type :String, 27 | raw_url :String, 28 | size :Long 29 | ) extends JsonToString[File] 30 | 31 | object File { 32 | implicit val fileCodecJson: CodecJson[File] = 33 | CodecJson.casecodec5(apply, unapply)( 34 | "filename", "language", "type", "raw_url", "size" 35 | ) 36 | } 37 | 38 | implicit val gistsCodecJson: CodecJson[Gists] = 39 | CodecJson.casecodec15(apply, unapply)( 40 | "files", "html_url", "forks_url", "comments", "created_at", "id", 41 | "owner", "commits_url", "git_pull_url", "public", "updated_at", 42 | "comments_url", "url", "description", "git_push_url" 43 | ) 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/main/scala/SearchCode.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class SearchCode( 4 | total_count : Long, 5 | items : List[SearchCode.Item] 6 | ) extends JsonToString[SearchCode] 7 | 8 | object SearchCode { 9 | 10 | implicit val searchCodeCodecJson: CodecJson[SearchCode] = 11 | CodecJson.casecodec2(apply, unapply)( 12 | "total_count", "items" 13 | ) 14 | 15 | final case class Item( 16 | name :String, 17 | path :String, 18 | sha :String, 19 | url :String, 20 | git_url :String, 21 | html_url :String, 22 | score :Double, 23 | repository :SearchCode.Repo 24 | ) extends JsonToString[Item] 25 | 26 | object Item { 27 | implicit val itemCodecJson: CodecJson[Item] = 28 | CodecJson.casecodec8(apply, unapply)( 29 | "name", "path", "sha", "url", "git_url", 30 | "html_url", "score", "repository" 31 | ) 32 | } 33 | 34 | final case class Repo( 35 | id :Long, 36 | name :String, 37 | full_name :String, 38 | owner :User, 39 | _private :Boolean, 40 | html_url :String, 41 | description :Option[String], 42 | fork :Boolean, 43 | url :String 44 | ) extends JsonToString[Repo] 45 | 46 | object Repo{ 47 | implicit val repoCodecJson: CodecJson[Repo] = 48 | CodecJson.casecodec9(apply, unapply)( 49 | "id", "name", "full_name", "owner", "private", 50 | "html_url", "description", "fork", "url" 51 | ) 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/main/scala/User.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class User( 4 | login :String, 5 | id :Long, 6 | avatar_url :String, 7 | gravatar_id :Option[String], 8 | url :String 9 | ) extends JsonToString[User] 10 | 11 | object User { 12 | 13 | implicit val userCodecJson: CodecJson[User] = 14 | CodecJson.casecodec5(apply, unapply)( 15 | "login", "id", "avatar_url", "gravatar_id", "url" 16 | ) 17 | 18 | } 19 | 20 | final case class Org( 21 | login :String, 22 | id :Long, 23 | avatar_url :String, 24 | url :String 25 | ) extends JsonToString[Org] 26 | 27 | object Org { 28 | 29 | implicit val orgCodecJson: CodecJson[Org] = 30 | CodecJson.casecodec4(apply, unapply)( 31 | "login", "id", "avatar_url", "url" 32 | ) 33 | 34 | } 35 | 36 | final case class Organization( 37 | `type` :String, 38 | avatar_url :String, 39 | blog :Option[String], 40 | company :Option[String], 41 | created_at :Option[DateTime], 42 | email :Option[String], 43 | followers :Long, 44 | following :Long, 45 | html_url :String, 46 | id :Long, 47 | location :Option[String], 48 | login :String, 49 | name :Option[String], 50 | public_gists :Long, 51 | public_repos :Long, 52 | url :String 53 | ) extends JsonToString[Organization] 54 | 55 | object Organization { 56 | 57 | implicit val organizationCodecJson: CodecJson[Organization] = 58 | CodecJson.casecodec16(apply, unapply)( 59 | "type", "avatar_url", "blog", "company", "created_at", "email", "followers", 60 | "following", "html_url", "id", "location", "login", "name", "public_gists", 61 | "public_repos", "url" 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /src/main/scala/Repo.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class SearchRepo( 4 | total_count :Long, 5 | items :List[Repo] 6 | ) extends JsonToString[SearchRepo] 7 | 8 | object SearchRepo { 9 | implicit val searchRepoCodecJson: CodecJson[SearchRepo] = 10 | CodecJson.casecodec2(apply, unapply)( 11 | "total_count", "items" 12 | ) 13 | } 14 | 15 | final case class Repo( 16 | has_downloads :Option[Boolean], 17 | name :String, 18 | has_issues :Boolean, 19 | forks :Int, 20 | `private` :Boolean, 21 | size :Int, 22 | open_issues :Int, 23 | url :String, 24 | description :Option[String], 25 | pushed_at :Option[DateTime], 26 | git_url :String, 27 | has_wiki :Boolean, 28 | fork :Boolean, 29 | id :Int, 30 | language :Option[String], 31 | homepage :Option[String], 32 | created_at :DateTime, 33 | html_url :String, 34 | updated_at :DateTime, 35 | watchers :Int, 36 | default_branch:Option[String], 37 | owner :User // TODO User or Org 38 | 39 | // full_name :String, 40 | // mirror_url :String, 41 | // svn_url :String, 42 | // clone_url :String, 43 | // ssh_url :String 44 | ) extends JsonToString[Repo] { 45 | def master: String = default_branch.getOrElse("master") 46 | } 47 | 48 | object Repo { 49 | 50 | implicit val repoCodecJson: CodecJson[Repo] = 51 | CodecJson.casecodec22(apply, unapply)( 52 | "has_downloads", 53 | "name", 54 | "has_issues", 55 | "forks", 56 | "private", 57 | "size", 58 | "open_issues", 59 | "url", 60 | "description", 61 | "pushed_at", 62 | "git_url", 63 | "has_wiki", 64 | "fork", 65 | "id", 66 | "language", 67 | "homepage", 68 | "created_at", 69 | "html_url", 70 | "updated_at", 71 | "watchers", 72 | "default_branch", 73 | "owner" 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /src/main/scala/Pull.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Pull( 4 | updated_at :DateTime, 5 | head :Pull.Ref, 6 | title :String, 7 | id :Long, 8 | created_at :DateTime, 9 | _links :PullLinks, 10 | merged_at :Option[DateTime], 11 | base :Pull.Ref, 12 | diff_url :String, 13 | body :String, 14 | state :String, 15 | html_url :String, 16 | issue_url :String, 17 | user :User, 18 | url :String, 19 | patch_url :String, 20 | number :Long, 21 | closed_at :Option[DateTime] 22 | ) extends JsonToString[Pull] 23 | 24 | final case class PullLinks( 25 | self :PullLinks.Link, 26 | review_comments :PullLinks.Link, 27 | issue :PullLinks.Link, 28 | html :PullLinks.Link, 29 | comments :PullLinks.Link, 30 | commits :PullLinks.Link, 31 | statuses :PullLinks.Link 32 | ) extends JsonToString[PullLinks] 33 | 34 | object PullLinks{ 35 | object Link { 36 | implicit val linkCodecJson: CodecJson[Link] = 37 | CodecJson.casecodec1(apply, unapply)("href") 38 | } 39 | 40 | final case class Link(href :String) extends AnyVal 41 | 42 | implicit val pullLinksCodecJson: CodecJson[PullLinks] = 43 | CodecJson.casecodec7(apply, unapply)( 44 | "self", "review_comments", "issue", "html", 45 | "comments", "commits", "statuses" 46 | ) 47 | } 48 | 49 | object Pull{ 50 | final case class Ref( 51 | user :Option[User], 52 | label :String, 53 | sha :String, 54 | repo :Option[Repo], 55 | ref :String 56 | ) extends JsonToString[Ref] 57 | 58 | object Ref { 59 | implicit val pullRefCodecJson: CodecJson[Ref] = 60 | CodecJson.casecodec5(apply, unapply)( 61 | "user", "label", "sha", "repo", "ref" 62 | ) 63 | } 64 | 65 | implicit val pullCodecJson: CodecJson[Pull] = 66 | CodecJson.casecodec18(apply, unapply)( 67 | "updated_at", "head", "title", "id", "created_at", "_links", 68 | "merged_at", "base", "diff_url", "body", "state", "html_url", 69 | "issue_url", "user", "url", "patch_url", "number", "closed_at" 70 | ) 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/main/scala/Commit.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Commit( 4 | tree :Commit.Tree, 5 | message :String, 6 | url :String, 7 | author :Commit.User, 8 | committer :Commit.User 9 | ) 10 | 11 | final case class CommitResponse( 12 | stats :Stats, 13 | commit :Commit, 14 | url :String, 15 | sha :String, 16 | author :User, 17 | committer :User, 18 | files :List[File], 19 | parents :List[Commit.Tree] 20 | ) extends JsonToString[CommitResponse] 21 | 22 | object CommitResponse { 23 | 24 | implicit val commitResponseCodecJson: CodecJson[CommitResponse] = 25 | CodecJson.casecodec8(apply, unapply)( 26 | "stats", "commit", "url", "sha", "author", 27 | "committer", "files", "parents" 28 | ) 29 | 30 | } 31 | 32 | final case class File( 33 | deletions :Long, 34 | changes :Long, 35 | additions :Long, 36 | status :String, 37 | raw_url :String, 38 | filename :String, 39 | blob_url :String, 40 | patch :String, 41 | sha :String 42 | ) 43 | 44 | object File { 45 | 46 | implicit val fileCodecJson: CodecJson[File] = 47 | CodecJson.casecodec9(apply, unapply)( 48 | "deletions", "changes", "additions", "status", "raw_url", 49 | "filename", "blob_url", "patch", "sha" 50 | ) 51 | 52 | } 53 | 54 | object Commit{ 55 | 56 | final case class Tree( 57 | sha :String, 58 | url :String 59 | ) extends JsonToString[Tree] 60 | 61 | object Tree { 62 | implicit val treeCodecJson: CodecJson[Tree] = 63 | CodecJson.casecodec2(apply, unapply)("sha", "url") 64 | } 65 | 66 | final case class User( 67 | email :String, 68 | date :String, 69 | name :String 70 | ) extends JsonToString[User] 71 | 72 | object User { 73 | implicit val userCodecJson: CodecJson[User] = 74 | CodecJson.casecodec3(apply, unapply)("email", "date", "name") 75 | } 76 | 77 | 78 | implicit val commitCodecJson: CodecJson[Commit] = 79 | CodecJson.casecodec5(apply, unapply)( 80 | "tree", "message", "url", "author", "committer" 81 | ) 82 | 83 | } 84 | 85 | final case class Stats( 86 | total :Long, 87 | deletions :Long, 88 | additions :Long 89 | ) extends JsonToString[Stats] 90 | 91 | object Stats { 92 | 93 | implicit val statsCodecJson: CodecJson[Stats] = 94 | CodecJson.casecodec3(apply, unapply)( 95 | "total", "deletions", "additions" 96 | ) 97 | 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/main/scala/Gist.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | final case class Gist( 4 | comments :Long, 5 | comments_url :String, 6 | commits_url :String, 7 | created_at :String, 8 | description :String, 9 | files :Map[String, Gist.File], 10 | forks :List[Gist.Fork], 11 | forks_url :String, 12 | git_pull_url :String, 13 | git_push_url :String, 14 | history :List[Gist.History], 15 | html_url :String, 16 | id :String, 17 | public :Boolean, 18 | updated_at :String, 19 | url :String, 20 | user :User 21 | ) 22 | 23 | object Gist { 24 | 25 | implicit val gistCodecJson: CodecJson[Gist] = 26 | CodecJson.casecodec17(apply, unapply)( 27 | "comments", "comments_url", "commits_url", "created_at", "description", 28 | "files", "forks", "forks_url", "git_pull_url", "git_push_url", "history", 29 | "html_url", "id", "public", "updated_at", "url", "user" 30 | ) 31 | 32 | final case class History( 33 | url :String, 34 | version :String, 35 | user :User, 36 | change_status :Gist.ChangeStatus, 37 | committed_at :String 38 | ) extends JsonToString[History] 39 | 40 | object History { 41 | implicit val historyCodecJson: CodecJson[History] = 42 | CodecJson.casecodec5(apply, unapply)( 43 | "url", "version", "user", "change_status", "committed_at" 44 | ) 45 | } 46 | 47 | final case class ChangeStatus( 48 | additions :Long, 49 | deletions :Long, 50 | total :Long 51 | ) extends JsonToString[ChangeStatus] 52 | 53 | object ChangeStatus { 54 | implicit val changeStatusCodecJson: CodecJson[ChangeStatus] = 55 | CodecJson.casecodec3(apply, unapply)( 56 | "additions", "deletions", "total" 57 | ) 58 | } 59 | 60 | final case class File( 61 | filename :String, 62 | language :Option[String], 63 | _type :String, 64 | raw_url :String, 65 | size :Long, 66 | content :String 67 | ) extends JsonToString[File] 68 | 69 | object File { 70 | implicit val fileCodecJson: CodecJson[File] = 71 | CodecJson.casecodec6(apply, unapply)( 72 | "filename", "language", "type", "raw_url", "size", "content" 73 | ) 74 | } 75 | 76 | final case class Fork( 77 | id :String, 78 | url :String, 79 | user :User, 80 | created_at :String, 81 | updated_at :String 82 | ) extends JsonToString[Fork] 83 | 84 | object Fork { 85 | implicit val forkJson: CodecJson[Fork] = 86 | CodecJson.casecodec5(apply, unapply)( 87 | "id", "url", "user", "created_at", "updated_at" 88 | ) 89 | } 90 | 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/test/scala/Main.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | import httpz._, scalajhttp._ 4 | 5 | import scalaz._ 6 | 7 | object Main { 8 | 9 | val program0 = ActionNelZipAp.apply2( 10 | Github.keys("xuwei-k").nel, 11 | Github.emails.nel 12 | )(Tuple2.apply) 13 | 14 | val program1 = ActionNelZipAp.apply12( 15 | Github.user.nel, 16 | Github.trees("scalaz", "scalaz", "afe19bcc3fe842b6eb15ee06f143d9cfca0b718b").nel, 17 | Github.repo("scalaz", "scalaz").nel, 18 | Github.commits("scalaz", "scalaz", "afe19bcc3fe842b6eb15ee06f143d9cfca0b718b").nel, 19 | Github.issues("scalaz", "scalaz").nel, 20 | Github.comments("scalaz", "scalaz").nel, 21 | Github.issueEvents("scalaz", "scalaz", 650).nel, 22 | Github.issueEvents("scalaz", "scalaz").nel, 23 | Github.readme("scalaz", "scalaz").nel, 24 | Github.org("scalaz").nel, 25 | Github.orgs.nel, 26 | Github.orgs("xuwei-k").nel 27 | )(Tuple12.apply) 28 | 29 | val program2 = ActionNelZipAp.apply12( 30 | Github.contributors("scalaz", "scalaz").nel, 31 | Github.followers.nel, 32 | Github.followers("xuwei-k").nel, 33 | Github.following.nel, 34 | Github.following("xuwei-k").nel, 35 | Github.repos.nel, 36 | Github.orgRepos("scalaz").nel, 37 | Github.pulls("scalaz", "scalaz").nel, 38 | Github.gists("xuwei-k").nel, 39 | Github.gists.me.nel, 40 | Github.gists.public.nel, 41 | Github.gists.starred.nel 42 | )(Tuple12.apply) 43 | 44 | def runProgram[F[_]: Monad, A]( 45 | p: ActionNel[A], interpreter: InterpreterF[F] 46 | )(f1: F[ErrorNel \/ A] => (ErrorNel \/ A), f2: F[ErrorNel \/ A] => Unit): Unit = { 47 | val r = p.run.foldMap(interpreter) 48 | val value = f1(r) 49 | value.swap.foreach{ errors => throw errors.head } 50 | f2(r) 51 | value.foreach(println) 52 | } 53 | 54 | def main(args: Array[String]): Unit = { 55 | import scalaz.syntax.equal._, std.anyVal._ 56 | 57 | implicit val timesMonad: Monad[Times] = 58 | scalaz.WriterT.writerMonad[List[Time]](scalaz.std.list.listMonoid) 59 | 60 | val conf = args match { 61 | case Array(token) => 62 | Request.header("Authorization", "token " + token) 63 | case _ => 64 | sys.env.get("TEST_USER_TOKEN") match { 65 | case Some(token) => 66 | Request.header("Authorization", "token " + token) 67 | case _ => 68 | emptyConfig 69 | } 70 | } 71 | 72 | runProgram( 73 | program0, ScalajInterpreter.sequential(conf).interpreter 74 | )(identity, identity) 75 | 76 | runProgram( 77 | program1, ScalajInterpreter.future(conf).interpreter 78 | )(_.unsafePerformSync, identity) 79 | 80 | runProgram( 81 | program2, ScalajInterpreter.times(conf).interpreter 82 | )(_.value, x => { 83 | val log = x.written 84 | log foreach println 85 | log.size assert_=== x.value.fold(errors => throw errors.head, _.productArity) 86 | }) 87 | } 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/main/scala/Issues.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | sealed abstract class State(private[ghscala] val name:String) 4 | case object Open extends State("open") 5 | case object Closed extends State("closed") 6 | object State{ 7 | def apply(name:String):State = name match { 8 | case Open.name => Open 9 | case Closed.name => Closed 10 | } 11 | } 12 | 13 | final case class PullRequest( 14 | patch_url :Option[String], 15 | diff_url :Option[String], 16 | html_url :Option[String] 17 | ) extends JsonToString[PullRequest] 18 | 19 | object PullRequest { 20 | 21 | implicit val pullRequestCodecJson: CodecJson[PullRequest] = 22 | CodecJson.casecodec3(apply, unapply)( 23 | "patch_url", "diff_url", "html_url" 24 | ) 25 | 26 | } 27 | 28 | final case class Label( 29 | color :String, 30 | url :String, 31 | name :String 32 | ) extends JsonToString[Label] 33 | 34 | object Label { 35 | 36 | implicit val labelCodecJson: CodecJson[Label] = 37 | CodecJson.casecodec3(apply, unapply)("color", "url", "name") 38 | 39 | } 40 | 41 | final case class Milestone( 42 | title :String, 43 | closed_issues :Long, 44 | due_on :Option[DateTime], 45 | number :Long, 46 | created_at :DateTime, 47 | description :Option[String], 48 | creator :User, 49 | state :String, 50 | id :Long, 51 | open_issues :Long, 52 | url :String 53 | ) extends JsonToString[Milestone] { 54 | lazy val getState:State = State(state) 55 | } 56 | 57 | object Milestone { 58 | 59 | implicit val issueCodecJson: CodecJson[Milestone] = 60 | CodecJson.casecodec11(apply, unapply)( 61 | "title", 62 | "closed_issues", 63 | "due_on", 64 | "number", 65 | "created_at", 66 | "description", 67 | "creator", 68 | "state", 69 | "id", 70 | "open_issues", 71 | "url" 72 | ) 73 | } 74 | 75 | final case class Issue( 76 | comments :Long, 77 | user :User, 78 | labels :List[Label], 79 | state :String, 80 | number :Long, 81 | pull_request :Option[PullRequest], 82 | milestone :Option[Milestone], 83 | assignee :Option[User], 84 | html_url :String, 85 | url :String, 86 | body :Option[String], 87 | closed_at :Option[DateTime], 88 | title :String, 89 | updated_at :DateTime, 90 | created_at :DateTime 91 | ) extends JsonToString[Issue] { 92 | 93 | lazy val getState: State = State(state) 94 | } 95 | 96 | object Issue { 97 | 98 | implicit val issueCodecJson: CodecJson[Issue] = 99 | CodecJson.casecodec15(apply, unapply)( 100 | "comments", 101 | "user", 102 | "labels", 103 | "state", 104 | "number", 105 | "pull_request", 106 | "milestone", 107 | "assignee", 108 | "html_url", 109 | "url", 110 | "body", 111 | "closed_at", 112 | "title", 113 | "updated_at", 114 | "created_at" 115 | ) 116 | 117 | } 118 | 119 | final case class IssueEvent( 120 | event :String, 121 | actor :User, 122 | id :Long, 123 | created_at :DateTime, 124 | commit_id :Option[String], 125 | url :String 126 | ) extends JsonToString[IssueEvent] { 127 | val eventType:EventType = EventType(event) 128 | } 129 | 130 | object IssueEvent { 131 | 132 | implicit val issueEventCodecJson: CodecJson[IssueEvent] = 133 | CodecJson.casecodec6(apply, unapply)( 134 | "event", "actor", "id", "created_at", "commit_id", "url" 135 | ) 136 | } 137 | 138 | final case class IssueEvent2( 139 | event :String, 140 | actor :User, 141 | issue :Issue, 142 | id :Long, 143 | commit_id :Option[String], 144 | created_at :DateTime, 145 | url :String 146 | ) extends JsonToString[IssueEvent2] { 147 | val eventType:EventType = EventType(event) 148 | } 149 | 150 | object IssueEvent2 { 151 | 152 | implicit val issueEvent2CodecJson: CodecJson[IssueEvent2] = 153 | CodecJson.casecodec7(apply, unapply)( 154 | "event", "actor", "issue", "id", "commit_id", "created_at", "url" 155 | ) 156 | } 157 | 158 | sealed abstract class EventType(private[ghscala] val name:String) 159 | object EventType{ 160 | case object Closed extends EventType("closed") 161 | case object Reopened extends EventType("reopened") 162 | case object Subscribed extends EventType("subscribed") 163 | case object Merged extends EventType("merged") 164 | case object Referenced extends EventType("referenced") 165 | case object Mentioned extends EventType("mentioned") 166 | case object Assigned extends EventType("assigned") 167 | case object Unknown extends EventType("unknown") 168 | 169 | def apply(name:String):EventType = name match{ 170 | case Closed.name => Closed 171 | case Reopened.name => Reopened 172 | case Subscribed.name => Subscribed 173 | case Merged.name => Merged 174 | case Referenced.name => Referenced 175 | case Mentioned.name => Mentioned 176 | case Assigned.name => Assigned 177 | case _ => Unknown 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._, Keys._ 2 | import sbtrelease._ 3 | import sbtrelease.ReleasePlugin.autoImport._ 4 | import ReleaseStateTransformations._ 5 | import xerial.sbt.Sonatype._ 6 | import com.typesafe.sbt.pgp.PgpKeys 7 | import sbtbuildinfo.BuildInfoPlugin 8 | import sbtbuildinfo.BuildInfoPlugin.autoImport._ 9 | 10 | object build { 11 | 12 | def gitHash: Option[String] = scala.util.Try( 13 | sys.process.Process("git rev-parse HEAD").lineStream_!.head 14 | ).toOption 15 | 16 | val sonatypeURL = "https://oss.sonatype.org/service/local/repositories/" 17 | 18 | val updateReadme = { state: State => 19 | val extracted = Project.extract(state) 20 | val scalaV = "2.13" 21 | val v = extracted get version 22 | val org = extracted get organization 23 | val modules = "ghscala" :: Nil 24 | val snapshotOrRelease = if(extracted get isSnapshot) "snapshots" else "releases" 25 | val readme = "README.md" 26 | val readmeFile = file(readme) 27 | val newReadme = Predef.augmentString(IO.read(readmeFile)).lines.map{ line => 28 | val matchReleaseOrSnapshot = line.contains("SNAPSHOT") == v.contains("SNAPSHOT") 29 | if(line.startsWith("libraryDependencies") && matchReleaseOrSnapshot){ 30 | val i = modules.indexWhere(line.contains) 31 | s"""libraryDependencies += "${org}" %% "${modules(i)}" % "$v"""" 32 | }else if(line.contains(sonatypeURL) && matchReleaseOrSnapshot){ 33 | val n = "ghscala" 34 | val javadocIndexHtml = "-javadoc.jar/!/index.html" 35 | val baseURL = s"${sonatypeURL}${snapshotOrRelease}/archive/${org.replace('.', '/')}/${n}_${scalaV}/${v}/${n}_${scalaV}-${v}" 36 | if(line.contains(javadocIndexHtml)){ 37 | s"- [API Documentation](${baseURL}${javadocIndexHtml})" 38 | }else line 39 | }else line 40 | }.mkString("", "\n", "\n") 41 | IO.write(readmeFile, newReadme) 42 | val git = new Git(extracted get baseDirectory) 43 | git.add(readme) ! state.log 44 | git.commit(message = "update " + readme, sign = false, signOff = false) ! state.log 45 | sys.process.Process("git diff HEAD^") ! state.log 46 | state 47 | } 48 | 49 | val updateReadmeProcess: ReleaseStep = updateReadme 50 | 51 | private[this] def Scala212 = "2.12.12" 52 | 53 | private[this] val unusedWarnings = ( 54 | "-Ywarn-unused:imports" :: 55 | Nil 56 | ) 57 | 58 | val baseSettings = sonatypeSettings ++ Seq( 59 | commands += Command.command("updateReadme")(updateReadme), 60 | releaseProcess := Seq[ReleaseStep]( 61 | checkSnapshotDependencies, 62 | inquireVersions, 63 | runClean, 64 | runTest, 65 | setReleaseVersion, 66 | commitReleaseVersion, 67 | updateReadmeProcess, 68 | tagRelease, 69 | ReleaseStep( 70 | action = state => Project.extract(state).runTask(PgpKeys.publishSigned, state)._1, 71 | enableCrossBuild = true 72 | ), 73 | setNextVersion, 74 | commitNextVersion, 75 | releaseStepCommand("sonatypeReleaseAll"), 76 | updateReadmeProcess, 77 | pushChanges 78 | ), 79 | fullResolvers ~= {_.filterNot(_.name == "jcenter")}, 80 | publishTo := Some( 81 | if (isSnapshot.value) 82 | Opts.resolver.sonatypeSnapshots 83 | else 84 | Opts.resolver.sonatypeStaging 85 | ), 86 | buildInfoKeys := Seq[BuildInfoKey]( 87 | organization, 88 | name, 89 | version, 90 | scalaVersion, 91 | sbtVersion, 92 | scalacOptions, 93 | licenses 94 | ), 95 | buildInfoKeys ++= Seq[BuildInfoKey]( 96 | "httpzVersion" -> httpzVersion 97 | ), 98 | buildInfoPackage := "ghscala", 99 | buildInfoObject := "BuildInfoGhScala", 100 | credentials ++= PartialFunction.condOpt(sys.env.get("SONATYPE_USER") -> sys.env.get("SONATYPE_PASS")){ 101 | case (Some(user), Some(pass)) => 102 | Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", user, pass) 103 | }.toList, 104 | organization := "com.github.xuwei-k", 105 | homepage := Some(url("https://github.com/xuwei-k/ghscala")), 106 | licenses := Seq("MIT License" -> url("http://www.opensource.org/licenses/mit-license.php")), 107 | scalacOptions ++= ( 108 | "-deprecation" :: 109 | "-unchecked" :: 110 | "-Xfuture" :: 111 | "-Xlint" :: 112 | "-language:existentials" :: 113 | "-language:higherKinds" :: 114 | "-language:implicitConversions" :: 115 | Nil 116 | ), 117 | scalacOptions ++= unusedWarnings, 118 | scalacOptions ++= PartialFunction.condOpt(CrossVersion.partialVersion(scalaVersion.value)){ 119 | case Some((2, v)) if v <= 12 => 120 | Seq("-Xfuture") 121 | }.toList.flatten, 122 | scalaVersion := Scala212, 123 | crossScalaVersions := Scala212 :: "2.13.4" :: Nil, 124 | scalacOptions in (Compile, doc) ++= { 125 | val tag = if(isSnapshot.value) gitHash.getOrElse("master") else { "v" + version.value } 126 | Seq( 127 | "-sourcepath", baseDirectory.value.getAbsolutePath, 128 | "-doc-source-url", s"https://github.com/xuwei-k/ghscala/tree/${tag}€{FILE_PATH}.scala" 129 | ) 130 | }, 131 | logBuffered in Test := false, 132 | pomExtra := ( 133 | 134 | 135 | xuwei-k 136 | Kenji Yoshida 137 | https://github.com/xuwei-k 138 | 139 | 140 | 141 | git@github.com:xuwei-k/ghscala.git 142 | scm:git:git@github.com:xuwei-k/ghscala.git 143 | {if(isSnapshot.value) gitHash.getOrElse("master") else { "v" + version.value }} 144 | 145 | ), 146 | pomPostProcess := { node => 147 | import scala.xml._ 148 | import scala.xml.transform._ 149 | def stripIf(f: Node => Boolean) = new RewriteRule { 150 | override def transform(n: Node) = 151 | if (f(n)) NodeSeq.Empty else n 152 | } 153 | val stripTestScope = stripIf { n => n.label == "dependency" && (n \ "scope").text == "test" } 154 | new RuleTransformer(stripTestScope).transform(node)(0) 155 | } 156 | ) ++ Seq(Compile, Test).flatMap(c => 157 | scalacOptions in (c, console) ~= {_.filterNot(unusedWarnings.toSet)} 158 | ) 159 | 160 | def httpzVersion = "0.6.0" 161 | } 162 | 163 | -------------------------------------------------------------------------------- /src/main/scala/Command.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | import argonaut.DecodeJson 4 | import httpz._ 5 | import scalaz.{Free, Inject, NonEmptyList} 6 | 7 | sealed abstract class Command[A](val f: String => Request)(implicit val decoder: DecodeJson[A]){ 8 | final def request: httpz.Request = 9 | requestWithURL(Github.baseURL) 10 | 11 | final def requestWithURL(baseURL: String): httpz.Request = 12 | f(baseURL) 13 | 14 | final def actionWithURL(baseURL: String): httpz.Action[A] = 15 | Core.json[A](requestWithURL(baseURL))(decoder) 16 | 17 | final def action: httpz.Action[A] = 18 | actionWithURL(Github.baseURL) 19 | 20 | final def lift[F[_]](implicit I: Inject[Command, F]): Free[F, A] = 21 | Free.liftF(I.inj(this)) 22 | 23 | final def actionEOps: ActionEOps[httpz.Error, A] = 24 | new ActionEOps(action) 25 | 26 | final def nel: ActionE[NonEmptyList[httpz.Error], A] = 27 | actionEOps.nel 28 | } 29 | 30 | object Command { 31 | 32 | private[ghscala] def get(url: String, opt: Config = httpz.emptyConfig): String => Request = { 33 | baseURL => opt(Request(url = baseURL + url, params = Map("per_page" -> "100"))) 34 | } 35 | 36 | case object Emojis extends Command[Map[String, String]]( 37 | get("emojis") 38 | ) 39 | 40 | final case class Tags(owner: String, repo: String) extends Command[List[ghscala.Tag]]( 41 | get(s"repos/$owner/$repo/tags") 42 | ) 43 | 44 | final case class Branches(owner: String, repo: String) extends Command[List[ghscala.Branch]]( 45 | get(s"repos/$owner/$repo/branches") 46 | ) 47 | 48 | final case class User(name: String) extends Command[ghscala.User]( 49 | get(s"users/$name") 50 | ) 51 | 52 | final case class Contributors(owner: String, repo: String) extends Command[List[ghscala.User]]( 53 | get(s"repos/${owner}/${repo}/contributors") 54 | ) 55 | 56 | final case class Followers(user: String) extends Command[List[ghscala.User]]( 57 | get(s"users/${user}/followers") 58 | ) 59 | 60 | final case class Following(user: String) extends Command[List[ghscala.User]]( 61 | get(s"users/${user}/following") 62 | ) 63 | 64 | final case class Blob(user: String, repo: String, sha: String) extends Command[ghscala.Blob]( 65 | get(s"repos/${user}/${repo}/git/blobs/${sha}") 66 | ) 67 | 68 | final case class Trees(user: String, repo: String, sha: String) extends Command[ghscala.Trees]( 69 | get(s"repos/${user}/${repo}/git/trees/${sha}") 70 | ) 71 | 72 | final case class Repos(user: String) extends Command[List[ghscala.Repo]]( 73 | get(s"users/${user}/repos") 74 | ) 75 | 76 | final case class OrgRepos(org: String) extends Command[List[ghscala.Repo]]( 77 | get(s"orgs/${org}/repos") 78 | ) 79 | 80 | final case class Repo(user: String, repo: String) extends Command[ghscala.Repo]( 81 | get(s"repos/${user}/${repo}") 82 | ) 83 | 84 | final case class Commits(user: String, repo: String, sha: String) extends Command[ghscala.CommitResponse]( 85 | get(s"repos/${user}/${repo}/commits/${sha}") 86 | ) 87 | 88 | final case class Issues(user: String, repo: String, state: State) extends Command[List[ghscala.Issue]]( 89 | get(s"repos/${user}/${repo}/issues", Request.param("state", state.name)) 90 | ) 91 | 92 | final case class RepoEvents(owner: String, repo: String) extends Command[List[ghscala.RepoEvent]]( 93 | get(s"repos/${owner}/${repo}/events") 94 | ) 95 | 96 | final case class IssueEvents(user: String, repo: String) extends Command[List[ghscala.IssueEvent2]]( 97 | get(s"repos/${user}/${repo}/issues/events") 98 | ) 99 | 100 | final case class IssueEvent(user: String, repo: String, number: Long) extends Command[List[ghscala.IssueEvent]]( 101 | get(s"repos/${user}/${repo}/issues/${number}/events") 102 | ) 103 | 104 | final case class Comments(user: String, repo: String) extends Command[List[ghscala.Comment]]( 105 | get(s"repos/${user}/${repo}/comments") 106 | ) 107 | 108 | final case class Readme(user: String, repo: String, ref: Option[String]) extends Command[ghscala.Contents]( 109 | get(s"repos/${user}/${repo}/readme", Request.paramOpt("ref", ref)) 110 | ) 111 | 112 | final case class Contents(user: String, repo: String, path: String, ref: Option[String]) extends Command[ghscala.Contents]( 113 | get(s"repos/${user}/${repo}/contents/${path}", Request.paramOpt("ref", ref)) 114 | ) 115 | 116 | final case class Org(name: String) extends Command[ghscala.Organization]( 117 | get(s"orgs/${name}") 118 | ) 119 | 120 | final case class Orgs(user: String) extends Command[List[ghscala.Org]]( 121 | get(s"users/${user}/orgs") 122 | ) 123 | 124 | final case class Pulls(user: String, repo: String, state: Option[State], baseBranch: Option[String]) extends Command[List[ghscala.Pull]]({ 125 | val p = Request.paramsOpt("state" -> state.map(_.name), "base" -> baseBranch) 126 | get(s"repos/${user}/${repo}/pulls", p) 127 | }) 128 | 129 | final case class Gists(user: String) extends Command[List[ghscala.Gists]]( 130 | get(s"users/${user}/gists") 131 | ) 132 | 133 | final case class Gist(id: String) extends Command[ghscala.Gist]( 134 | get(s"gists/${id}") 135 | ) 136 | 137 | final case class Subscribers(owner: String, repo: String) extends Command[List[ghscala.User]]( 138 | get(s"repos/${owner}/${repo}/subscribers") 139 | ) 140 | 141 | final case class Keys(user: String) extends Command[List[ghscala.PublicKey]]( 142 | get(s"users/${user}/keys") 143 | ) 144 | 145 | case object Emails extends Command[List[ghscala.Email]]( 146 | get("user/emails") 147 | ) 148 | 149 | case object GitignoreTemplates extends Command[List[String]]( 150 | get("gitignore/templates") 151 | ) 152 | 153 | final case class Gitignore(language: String) extends Command[ghscala.Gitignore]( 154 | get(s"gitignore/templates/${language}") 155 | ) 156 | 157 | case object Public extends Command[List[ghscala.Gists]]( 158 | get("gists/public") 159 | ) 160 | 161 | final case class SearchRepositories(query: String, sort: SearchRepoSort) extends Command[ghscala.SearchRepo]({ 162 | val p = Request.paramOpt("sort", sort.name) andThen Request.param("q", query) 163 | get("search/repositories", p) 164 | }) 165 | 166 | final case class SearchCode(query: String) extends Command[ghscala.SearchCode]( 167 | get("search/code", Request.param("q", query)) 168 | ) 169 | 170 | final case class SearchIssues(query: String) extends Command[ghscala.SearchIssues]( 171 | get("search/issues", Request.param("q", query)) 172 | ) 173 | } 174 | 175 | object SelfCommand { 176 | import Command.get 177 | 178 | case object User extends Command[ghscala.User]( 179 | get("user") 180 | ) 181 | 182 | /* 183 | case object Follow extends Command[List[ghscala.User]]( 184 | get("") // TODO 185 | ) 186 | */ 187 | 188 | case object Followers extends Command[List[ghscala.User]]( 189 | get("user/following") 190 | ) 191 | 192 | case object Following extends Command[List[ghscala.User]]( 193 | get("user/followers") 194 | ) 195 | 196 | case object Repos extends Command[List[ghscala.Repo]]( 197 | get("user/repos") 198 | ) 199 | 200 | case object Orgs extends Command[List[ghscala.Org]]( 201 | get("user/orgs") 202 | ) 203 | 204 | case object Gists extends Command[List[ghscala.Gists]]( 205 | get("gists") 206 | ) 207 | 208 | case object Starred extends Command[List[ghscala.Gists]]( 209 | get("gists/starred") 210 | ) 211 | } 212 | -------------------------------------------------------------------------------- /src/main/scala/Github.scala: -------------------------------------------------------------------------------- 1 | package ghscala 2 | 3 | import httpz._ 4 | import scalaz.{Inject, Free} 5 | 6 | object GhScala extends Github[Command, ({type l[a] = Free[Command, a]})#l] { 7 | override protected[this] def f[A](c: Command[A]) = lift(c) 8 | } 9 | 10 | object Github extends Github[Command, Action]{ 11 | implicit def instance[F[_]](implicit I: Inject[Command, F]): Github[F, ({type l[a] = Free[F, a]})#l] = 12 | new Github[F, ({type l[a] = Free[F, a]})#l] { 13 | def f[A](c: Command[A]) = lift(c) 14 | } 15 | 16 | def commands2Action[A](a: Free[Command, A]): Action[A] = 17 | a.foldMap(Interpreter)(httpz.ActionMonad) 18 | 19 | protected[this] override def f[A](c: Command[A]) = 20 | commands2Action(lift(c)) 21 | 22 | private[ghscala] final val baseURL = "https://api.github.com/" 23 | } 24 | 25 | sealed abstract class Github[F[_], G[_]](implicit I: Inject[Command, F]) { 26 | 27 | final type FreeF[A] = Free[F, A] 28 | 29 | final def lift[A](f: Command[A]): FreeF[A] = 30 | Free.liftF(I.inj(f)) 31 | 32 | protected[this] def f[A](c: Command[A]): G[A] 33 | 34 | /** [[https://developer.github.com/v3/emojis/]] */ 35 | final val emojis: G[Map[String, String]] = 36 | f(Command.Emojis) 37 | 38 | /** [[http://developer.github.com/v3/repos/#list-tags]] */ 39 | def tags(owner: String, repo: String): G[List[Tag]] = 40 | f(Command.Tags(owner, repo)) 41 | 42 | /** [[http://developer.github.com/v3/repos/#list-branches]] */ 43 | def branches(owner: String, repo: String): G[List[Branch]] = 44 | f(Command.Branches(owner, repo)) 45 | 46 | /** [[http://developer.github.com/v3/users/]] */ 47 | final val user: G[User] = 48 | f(SelfCommand.User) 49 | 50 | /** [[http://developer.github.com/v3/users/]] */ 51 | def user(user: String): G[User] = 52 | f(Command.User(user)) 53 | 54 | /** [[http://developer.github.com/v3/repos/#list-contributors]] */ 55 | def contributors(owner: String, repo: String): G[List[User]] = 56 | f(Command.Contributors(owner, repo)) 57 | 58 | /** [[http://developer.github.com/v3/users/followers/#list-followers-of-a-user]] */ 59 | final val followers: G[List[User]] = 60 | f(SelfCommand.Followers) 61 | 62 | /** [[http://developer.github.com/v3/users/followers/#list-followers-of-a-user]] */ 63 | def followers(user: String): G[List[User]] = 64 | f(Command.Followers(user)) 65 | 66 | /** [[http://developer.github.com/v3/users/followers/#list-users-followed-by-another-user]] */ 67 | final val following: G[List[User]] = 68 | f(SelfCommand.Following) 69 | 70 | /** [[http://developer.github.com/v3/users/followers/#list-users-followed-by-another-user]] */ 71 | def following(user: String): G[List[User]] = 72 | f(Command.Following(user)) 73 | 74 | /** [[http://developer.github.com/v3/git/blobs]] */ 75 | def blob(user: String, repo: String, sha: String): G[Blob] = 76 | f(Command.Blob(user, repo, sha)) 77 | 78 | /** [[http://developer.github.com/v3/git/trees]] */ 79 | def trees(user: String, repo: String, sha: String): G[Trees] = 80 | f(Command.Trees(user, repo, sha)) 81 | 82 | /** [[http://developer.github.com/v3/repos/#list-your-repositories]] */ 83 | final val repos: G[List[Repo]] = 84 | f(SelfCommand.Repos) 85 | 86 | /** [[http://developer.github.com/v3/repos/#list-user-repositories]] */ 87 | def repos(user: String): G[List[Repo]] = 88 | f(Command.Repos(user)) 89 | 90 | /** [[http://developer.github.com/v3/repos/#list-organization-repositories]] */ 91 | def orgRepos(org: String): G[List[Repo]] = 92 | f(Command.OrgRepos(org)) 93 | 94 | /** [[http://developer.github.com/v3/repos/#list-user-repositories]] */ 95 | def repo(user: String, repo: String): G[Repo] = 96 | f(Command.Repo(user, repo)) 97 | 98 | /** [[http://developer.github.com/v3/git/commits]] */ 99 | def commits(user: String, repo: String, sha: String): G[CommitResponse] = 100 | f(Command.Commits(user, repo, sha)) 101 | 102 | /** [[http://developer.github.com/v3/issues]] */ 103 | def issues(user: String, repo: String, state: State = Open): G[List[Issue]] = 104 | f(Command.Issues(user, repo, state)) 105 | 106 | /** [[https://developer.github.com/v3/activity/events/#list-repository-events]] */ 107 | def repoEvents(owner: String, repo: String): G[List[RepoEvent]] = 108 | f(Command.RepoEvents(owner, repo)) 109 | 110 | /** [[http://developer.github.com/v3/issues/events/]] */ 111 | def issueEvents(user: String, repo: String, number: Long): G[List[IssueEvent]] = 112 | f(Command.IssueEvent(user, repo, number)) 113 | 114 | /** [[http://developer.github.com/v3/issues/events/]] */ 115 | def issueEvents(user: String, repo: String): G[List[IssueEvent2]] = 116 | f(Command.IssueEvents(user, repo)) 117 | 118 | /** [[http://developer.github.com/v3/repos/comments]] */ 119 | def comments(user: String, repo: String): G[List[Comment]] = 120 | f(Command.Comments(user, repo)) 121 | 122 | /** [[http://developer.github.com/v3/repos/contents]] */ 123 | def readme(user: String, repo: String, ref: String): G[Contents] = 124 | f(Command.Readme(user, repo, Option(ref))) 125 | 126 | /** [[http://developer.github.com/v3/repos/contents]] */ 127 | def readme(user: String, repo: String): G[Contents] = 128 | f(Command.Readme(user, repo, None)) 129 | 130 | /** [[http://developer.github.com/v3/repos/contents]] */ 131 | def contents(user: String, repo: String, path: String): G[Contents] = 132 | f(Command.Contents(user, repo, path, None)) 133 | 134 | /** [[http://developer.github.com/v3/repos/contents]] */ 135 | def contents(user: String, repo: String, path: String, ref: String): G[Contents] = 136 | f(Command.Contents(user, repo, path, Option(ref))) 137 | 138 | /** [[http://developer.github.com/v3/orgs]] */ 139 | def org(orgName: String): G[Organization] = 140 | f(Command.Org(orgName)) 141 | 142 | /** [[http://developer.github.com/v3/orgs]] */ 143 | def orgs(user: String): G[List[Org]] = 144 | f(Command.Orgs(user)) 145 | 146 | /** [[http://developer.github.com/v3/orgs]] */ 147 | final val orgs: G[List[Org]] = 148 | f(SelfCommand.Orgs) 149 | 150 | /** [[http://developer.github.com/v3/pulls]] */ 151 | def pulls(user: String, repo: String): G[List[Pull]] = 152 | f(Command.Pulls(user, repo, None, None)) 153 | 154 | /** [[http://developer.github.com/v3/pulls]] */ 155 | def pulls(user: String, repo: String, state: State = Open, baseBranch: String = null): G[List[Pull]] = 156 | f(Command.Pulls(user, repo, Option(state), Option(baseBranch))) 157 | 158 | /** [[http://developer.github.com/v3/gists/#list-gists]] */ 159 | def gists(user: String): G[List[Gists]] = 160 | f(Command.Gists(user)) 161 | 162 | /** [[http://developer.github.com/v3/gists/#get-a-single-gist]] */ 163 | def gist(id: String): G[Gist] = 164 | f(Command.Gist(id)) 165 | 166 | def markdown(text: String): ActionE[Throwable, String] = { 167 | import argonaut.Json 168 | Core.string(Request( 169 | url = Github.baseURL + "markdown", 170 | method = "POST", 171 | body = Some( 172 | Json.obj("text" -> Json.jString(text)).toString.getBytes 173 | ) 174 | )) 175 | } 176 | 177 | /** [[https://developer.github.com/v3/activity/watching/#list-watchers]] */ 178 | def subscribers(owner: String, repo: String): G[List[User]] = 179 | f(Command.Subscribers(owner, repo)) 180 | 181 | /** [[https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user]] */ 182 | def keys(user: String): G[List[PublicKey]] = 183 | f(Command.Keys(user)) 184 | 185 | /** [[https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user]] */ 186 | final val emails: G[List[Email]] = 187 | f(Command.Emails) 188 | 189 | object gitignore { 190 | final val templates: G[List[String]] = 191 | f(Command.GitignoreTemplates) 192 | 193 | def apply(language: String): G[Gitignore] = 194 | f(Command.Gitignore(language)) 195 | } 196 | 197 | object gists { 198 | /** [[http://developer.github.com/v3/gists/#list-gists]] */ 199 | final val me: G[List[Gists]] = 200 | f(SelfCommand.Gists) 201 | 202 | /** [[http://developer.github.com/v3/gists/#list-gists]] */ 203 | final val public: G[List[Gists]] = 204 | f(Command.Public) 205 | 206 | /** [[http://developer.github.com/v3/gists/#list-gists]] */ 207 | final val starred: G[List[Gists]] = 208 | f(SelfCommand.Starred) 209 | } 210 | 211 | object search { 212 | /** [[http://developer.github.com/v3/search/#search-repositories]] */ 213 | def repositories(query: String, sort: SearchRepoSort = SearchRepoSort.Default): G[SearchRepo] = 214 | f(Command.SearchRepositories(query, sort)) 215 | 216 | /** [[http://developer.github.com/v3/search/#search-code]] */ 217 | def code(query: String): G[SearchCode] = 218 | f(Command.SearchCode(query)) 219 | 220 | // TODO sort, order 221 | /** [[http://developer.github.com/v3/search/#search-issues]] */ 222 | def issues(query: String): G[SearchIssues] = 223 | f(Command.SearchIssues(query)) 224 | } 225 | } 226 | 227 | --------------------------------------------------------------------------------