Last active
October 9, 2024 18:08
-
-
Save dacr/88966cba361061be20d50534711a6c2a to your computer and use it in GitHub Desktop.
Publish code examples to remote repositories solutions (DEPRECATED see https://github.com/dacr/code-examples-manager). / published by https://github.com/dacr/code-examples-manager #82d07ca5-6b9c-4f6e-bc48-23ca7af16835/7cfeb0ec7a185c0726e4936ab3ae6b58d68a71af
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
#!/usr/bin/env amm | |
// summary : Publish code examples to remote repositories solutions (DEPRECATED see https://github.com/dacr/code-examples-manager). | |
// keywords : scala, github-api, gitlab-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 : 82d07ca5-6b9c-4f6e-bc48-23ca7af16835 | |
// 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' | |
//> using scala 2.12.20 | |
//> using dep com.github.pathikrit::better-files:3.8.0 | |
//> using dep org.scalatest::scalatest:3.0.8 | |
//> using dep com.softwaremill.sttp::core:1.6.4 | |
//> using dep com.softwaremill.sttp::json4s:1.6.4 | |
//> using dep com.softwaremill.sttp::okhttp-backend:1.6.4 | |
//> using dep org.json4s::json4s-native:3.6.7 | |
import better.files._ | |
import better.files.Dsl._ | |
import org.scalatest._ | |
import org.scalatest.OptionValues._ | |
import com.softwaremill.sttp.Uri | |
import com.softwaremill.sttp.json4s._ | |
import com.softwaremill.sttp._ | |
import scala.util.{Either, Left, Right} | |
import org.json4s.JValue | |
object Helpers { | |
implicit val serialization = org.json4s.native.Serialization | |
implicit val formats = org.json4s.DefaultFormats | |
def sha1(that: String): String = { | |
// Inspired from https://alvinalexander.com/source-code/scala-method-create-md5-hash-of-string | |
import java.security.MessageDigest | |
import java.math.BigInteger | |
val md = MessageDigest.getInstance("SHA-1") | |
val digest = md.digest(that.getBytes) | |
val bigInt = new BigInteger(1, digest) | |
val hashedString = bigInt.toString(16) | |
hashedString | |
} | |
} | |
import Helpers._ | |
// =================================================================================== | |
// GITHUB MODEL | |
case class GistFileInfo( | |
filename: String, | |
`type`: String, | |
language: String, | |
raw_url: String, | |
size: Int, | |
) | |
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, | |
) | |
case class Gist( | |
id: String, | |
description: String, | |
html_url: String, | |
public: Boolean, | |
files: Map[String, GistFile], | |
) | |
case class GistFileSpec( | |
filename: String, | |
content: String | |
) | |
case class GistSpec( | |
description: String, | |
public: Boolean, | |
files: Map[String, GistFileSpec], | |
) | |
trait httpAPIOperations { | |
implicit val sttpBackend = com.softwaremill.sttp.okhttp.OkHttpSyncBackend() | |
} | |
object GitHubGistsOperations extends httpAPIOperations { | |
case class AuthToken(value: String) { | |
override def toString: String = value | |
} | |
def getUserGists(user: String)(implicit token: AuthToken): 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: AuthToken): 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: AuthToken): 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: AuthToken): 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] | |
} | |
} | |
} | |
// =================================================================================== | |
case class CodeExample( | |
file: File, | |
summary: Option[String], | |
keywords: List[String], | |
publish: List[String], | |
authors: List[String], | |
id: Option[String], | |
) { | |
def content: String = file.contentAsString | |
def filename: String = file.name | |
def fileExt: String = filename.split("[.]", 2).drop(1).headOption.getOrElse("") | |
} | |
object CodeExample { | |
def extractValue(from: String)(key: String): Option[String] = { | |
val RE = ("""(?m)(?i)^(?:(?://)|(?:##))\s+""" + key + """\s+:\s+(.*)$""").r | |
RE.findFirstIn(from).collect { case RE(value) => value.trim } | |
} | |
def extractValueList(from: String)(key: String): List[String] = { | |
extractValue(from)(key).map(_.split("""[ \t\r,;]+""").toList).getOrElse(Nil) | |
} | |
def apply(file: File): CodeExample = { | |
val content = file.contentAsString | |
CodeExample( | |
file = file, | |
summary = extractValue(content)("summary"), | |
keywords = extractValueList(content)("keywords"), | |
publish = extractValueList(content)("publish"), | |
authors = extractValueList(content)("authors"), | |
id = extractValue(content)("id"), | |
) | |
} | |
} | |
trait CodeExampleStatus { | |
val id: String | |
val example: CodeExample | |
val contentHash: String | |
} | |
case class GitHubGistCodeExampleStatus( | |
id:String, | |
example:CodeExample, | |
contentHash:String | |
) extends CodeExampleStatus | |
case class CodeExamples( | |
status: List[CodeExampleStatus] | |
) | |
object ExamplesManager { | |
def examples(): List[CodeExample] = { | |
pwd | |
.glob("*.{sc,sh}") | |
.map(CodeExample(_)) | |
.toList | |
.filter(_.id.isDefined) | |
} | |
def updateGitHubGistRemoteExamples(): Unit = { | |
import GitHubGistsOperations._ | |
implicit val token = AuthToken(scala.util.Properties.envOrElse("GIST_TOKEN", "invalid-token")) | |
val user = "dacr" | |
val remoteGists = getUserGists(user).toList | |
} | |
} | |
// =================================================================================== | |
object ExamplesManagerTest extends FlatSpec with Matchers { | |
override def suiteName: String = "ExamplesManagerTest" | |
"CodeExample" can "be constructed from a local file" in { | |
val ex1 = CodeExample(file"poc-1-examples-manager.sc") | |
ex1.fileExt shouldBe "sc" | |
ex1.filename shouldBe "poc-1-examples-manager.sc" | |
ex1.content should include regex "id [:] 82d07ca5-6b9c-4f6e-bc48-23ca7af16835" | |
} | |
"All examples" should "all have a summary" in { | |
for {example <- ExamplesManager.examples()} { | |
example.summary shouldBe 'defined | |
} | |
} | |
it should "have a distinct identifier" in { | |
val examplesById = ExamplesManager.examples().groupBy(_.id) | |
for { (id, examplesWithThisId) <- examplesById} { | |
examplesWithThisId should have size(1) | |
} | |
} | |
"ExamplesManager" should "be able to list locally available examples" in { | |
val examplesByFileExt = | |
ExamplesManager | |
.examples() | |
.groupBy(_.fileExt) | |
.mapValues(_.size) | |
examplesByFileExt.get("sc").get should be > 0 | |
examplesByFileExt.get("sh").get should be > 0 | |
} | |
it should "be able to get a gist example using its IDs" in { | |
} | |
it should "be able to synchronize remotes" in { | |
} | |
} | |
// =================================================================================== | |
//run(ExamplesManagerTest) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment