Last active
October 9, 2024 18:08
-
-
Save dacr/c525e743825ef10afe1e6bc72737554b to your computer and use it in GitHub Desktop.
Publish code sample as github gist (DEPRECATED see https://github.com/dacr/code-examples-manager). / published by https://github.com/dacr/code-examples-manager #e4b1937b-e327-447c-ac75-9df5d4ede340/8f44b4d9cf4728ec12d3b095a5fe78826a2e9ecc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// summary : Publish code sample as github gist (DEPRECATED see https://github.com/dacr/code-examples-manager). | |
// keywords : scala, github-api, automation, sttp, json4s, betterfiles, gists-api | |
// publish : gist | |
// authors : David Crosson | |
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2) | |
// id : e4b1937b-e327-447c-ac75-9df5d4ede340 | |
// created-on : 2020-05-31T19:54:52Z | |
// managed-by : https://github.com/dacr/code-examples-manager | |
// execution : scala 2.12 ammonite script (http://ammonite.io/) - run as follow 'amm scriptname.sc' | |
/* Get an authorized access to github gist API : | |
- list authorizations : curl --user "dacr" https://api.github.com/authorizations | |
- create token : curl https://api.github.com/authorizations --user "dacr" --data '{"scopes":["gist"],"note":"testoauth"}' | |
- setup GIST_TOKEN environment variable with the previously generated token | |
- get token : not possible of course | |
- interesting link : https://gist.github.com/joyrexus/85bf6b02979d8a7b0308#oauth | |
*/ | |
//> using scala 2.12.20 | |
//> using dep com.softwaremill.sttp::core:1.5.17 | |
//> using dep com.softwaremill.sttp::json4s:1.5.17 | |
//> using dep com.softwaremill.sttp::okhttp-backend:1.5.17 | |
//> using dep org.json4s::json4s-native:3.6.5 | |
//> using dep org.scalatest::scalatest:3.0.6 | |
//> using dep com.github.pathikrit::better-files:3.7.1 | |
import com.softwaremill.sttp.Uri | |
import com.softwaremill.sttp.json4s._ | |
import com.softwaremill.sttp._ | |
import scala.util.{Either, Left, Right} | |
import org.json4s.JValue | |
import better.files._ | |
import better.files.Dsl._ | |
implicit val sttpBackend = com.softwaremill.sttp.okhttp.OkHttpSyncBackend() | |
implicit val serialization = org.json4s.native.Serialization | |
implicit val formats = org.json4s.DefaultFormats | |
trait CommonBase { | |
def filename:String | |
val basename = filename.split("[.]",2).head | |
} | |
trait CommonContentBase extends CommonBase{ | |
def content:String | |
def hash:Int = scala.util.hashing.MurmurHash3.stringHash(basename + content) | |
def uuid:String = { | |
val RE = ("""(?m)(?i)^//\s+id\s+:\s+(.*)$""").r | |
RE.findFirstIn(content).collect { case RE(value) => value.trim }.get // Make it fail if no uuid was provided | |
} | |
} | |
case class GistFileInfo( | |
filename: String, | |
`type`: String, | |
language: String, | |
raw_url: String, | |
size: Int, | |
) extends CommonBase | |
case class GistInfo( | |
id: String, | |
description: String, | |
html_url: String, | |
public: Boolean, | |
files: Map[String, GistFileInfo], | |
) | |
case class GistFile( | |
filename: String, | |
`type`: String, | |
language: String, | |
raw_url: String, | |
size: Int, | |
truncated: Boolean, | |
content: String, | |
) extends CommonContentBase | |
case class Gist( | |
id: String, | |
description: String, | |
html_url: String, | |
public: Boolean, | |
files: Map[String, GistFile], | |
) | |
case class GistFileSpec( | |
filename: String, | |
content: String | |
) extends CommonContentBase | |
case class GistSpec( | |
description: String, | |
public: Boolean, | |
files: Map[String, GistFileSpec], | |
) | |
case class Token(value: String) { | |
override def toString: String = value | |
} | |
def getUserGists(user: String)(implicit token: Token): Stream[GistInfo] = { | |
val nextLinkRE =""".*<([^>]+)>; rel="next".*""".r | |
def worker(nextQuery: Option[Uri], currentRemaining: Iterable[GistInfo]): Stream[GistInfo] = { | |
(nextQuery, currentRemaining) match { | |
case (None, Nil) => Stream.empty | |
case (_, head :: tail) => head #:: worker(nextQuery, tail) | |
case (Some(query), Nil) => | |
val response = { | |
sttp | |
.get(query) | |
.header("Authorization", s"token $token") | |
.response(asJson[Array[GistInfo]]) | |
.send() | |
} | |
response.body match { | |
case Left(message) => | |
System.err.println(s"List gists - Something wrong has happened : $message") | |
Stream.empty | |
case Right(gistsArray) => | |
val next = response.header("Link") // it provides the link for the next & last page :) | |
val newNextQuery = next.collect { case nextLinkRE(uri) => uri"$uri" } | |
worker(newNextQuery, gistsArray.toList) | |
} | |
} | |
} | |
val count = 10 | |
val startQuery = uri"https://api.github.com/users/$user/gists?page=1&per_page=$count" | |
worker(Some(startQuery), Nil) | |
} | |
def getGist(id: String)(implicit token: Token): Option[Gist] = { | |
val query = uri"https://api.github.com/gists/$id" | |
val response = { | |
sttp | |
.get(query) | |
.header("Authorization", s"token $token") | |
.response(asJson[Gist]) | |
.send() | |
} | |
response.body match { | |
case Left(message) => | |
System.err.println(s"Get gist - Something wrong has happened : $message") | |
None | |
case Right(gist) => | |
Some(gist) | |
} | |
} | |
def addGist(gist: GistSpec)(implicit token: Token): Option[String] = { | |
val query = uri"https://api.github.com/gists" | |
val response = { | |
sttp | |
.body(gist) | |
.post(query) | |
.header("Authorization", s"token $token") | |
.response(asJson[JValue]) | |
.send() | |
} | |
response.body match { | |
case Left(message) => | |
System.err.println(s"Add gist - Something wrong has happened : $message") | |
None | |
case Right(jvalue) => | |
(jvalue \ "id").extractOpt[String] | |
} | |
} | |
def updateGist(id: String, gist: GistSpec)(implicit token: Token): Option[String] = { | |
val query = uri"https://api.github.com/gists/$id" | |
val response = { | |
sttp | |
.body(gist) | |
.patch(query) | |
.header("Authorization", s"token $token") | |
.response(asJson[JValue]) | |
.send() | |
} | |
response.body match { | |
case Left(message) => | |
System.err.println(s"Update gist - Something wrong has happened : $message") | |
None | |
case Right(jvalue) => | |
(jvalue \ "id").extractOpt[String] | |
} | |
} | |
implicit val token = Token(scala.util.Properties.envOrElse("GIST_TOKEN", "invalid-token")) | |
val user = "dacr" | |
val remoteGists = getUserGists(user).toList | |
case class LocalScript( | |
filename: String, | |
summary: Option[String], | |
keywords: List[String], | |
publish: List[String], | |
authors: List[String], | |
id: Option[String], | |
content: String) extends CommonContentBase | |
object LocalScript { | |
def apply(filename: String, content: String): LocalScript = { | |
def extractValue(key: String): Option[String] = { | |
val RE = ("""(?m)(?i)^//\s+"""+key+"""\s+:\s+(.*)$""").r | |
RE.findFirstIn(content).collect { case RE(value) => value.trim } | |
} | |
def extractValueList(key: String): List[String] = { | |
extractValue(key).map(_.split("""[ \t\r,;]+""").toList).getOrElse(Nil) | |
} | |
val summary: Option[String] = extractValue("summary") | |
val keywords: List[String] = extractValueList("keywords") | |
val publish: List[String] = extractValueList("publish") | |
val authors: List[String] = extractValueList("authors") | |
val id: Option[String] = extractValue("id") | |
LocalScript( | |
filename = filename, | |
summary = summary, | |
keywords = keywords, | |
publish = publish, | |
authors = authors, | |
id = id, | |
content = content, | |
) | |
} | |
} | |
def hasDuplicateUuids(scripts:List[LocalScript]):Boolean = { | |
val scriptsByUuid = scripts.filter(_.id.isDefined).groupBy(_.id) | |
scriptsByUuid.exists{case (Some(k), v) => v.size > 1} | |
} | |
def fileHasChanged(script:LocalScript, remote:Gist):Boolean = { | |
// it only manages one file by gist ! | |
remote.files.values.headOption.filter(_.hash != script.hash).isDefined | |
} | |
// TODO : REFACTOR, that code smells bad | |
def checkAndOperateGist(script: LocalScript, remoteGists:List[GistInfo]):Unit = { | |
// TODO : base update check on UUID instead of file basename !! | |
val remoteGistInfoOption = remoteGists.find(_.files.values.exists(_.basename == script.basename)) | |
val filename = script.basename+".scala" | |
val summary = script.summary.getOrElse("") | |
remoteGistInfoOption match { | |
case Some(gistInfo) => // Then check for update | |
getGist(gistInfo.id) match { | |
case None => | |
println(s"Couldn't get ${gistInfo.id} gist details :( ") | |
case Some(remoteGist) if fileHasChanged(script, remoteGist) => | |
val updatedGistFilename = filename | |
val updatedGistFile = GistFileSpec(updatedGistFilename, script.content) | |
val updatedGist = GistSpec( | |
description = summary, | |
public = true, // TODO : manage public:private gists | |
files = Map(updatedGistFilename -> updatedGistFile) | |
) | |
val result = updateGist(remoteGist.id, updatedGist) | |
println(s"Just updated $filename, gistId : $result") | |
case Some(remoteGist) => | |
//println(s"No change for $filename, gistId : ${remoteGist.id}") | |
} | |
case None => //Then Add | |
val spec = GistSpec( | |
summary, | |
true, // TODO : manage public:private gists | |
Map( | |
filename -> GistFileSpec(filename, script.content) | |
) | |
) | |
val generatedId:Option[String] = addGist(spec) | |
println(s"Just added $filename, gistId : $generatedId") | |
} | |
} | |
val scripts = pwd.glob("*.sc").map(file => LocalScript(file.name, file.contentAsString)).toList | |
val gistScripts = scripts.filter(_.publish.contains("gist")) | |
val foundDuplicatedUuids = hasDuplicateUuids(scripts) | |
assert(foundDuplicatedUuids == false) | |
//scripts.foreach{sc => println(sc.filename+" : "+sc.summary.getOrElse("N/A") + "("+sc.keywords.mkString(",")+")")} | |
println("Not at all published scripts :") | |
scripts.filter(_.summary.isEmpty).sortBy(_.basename).foreach{ sc => | |
println(" "+sc.filename) | |
} | |
println("available scripts : " + scripts.size) | |
println("duplicates check : " + foundDuplicatedUuids) | |
println("publishable gists : " + gistScripts.size) | |
for { | |
gistScript <- gistScripts //.filter(_.basename == "testing-pi") | |
} { | |
// script disabled (DEPRECATED see https://github.com/dacr/code-examples-manager). | |
// checkAndOperateGist(gistScript, remoteGists) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment