Skip to content

Instantly share code, notes, and snippets.

@Polkas
Last active September 1, 2024 09:02
Show Gist options
  • Select an option

  • Save Polkas/2b656d4a4497054a1b931cd3ae12ba1e to your computer and use it in GitHub Desktop.

Select an option

Save Polkas/2b656d4a4497054a1b931cd3ae12ba1e to your computer and use it in GitHub Desktop.
pyshiny - compare R package DESCRIPTION files
import pyodide.http
from shiny import App, reactive, render, ui, req
from htmltools import TagList, div
from htmltools.tags import th, td, tr, thead, tbody, table, style, head, span, a
from re import compile, MULTILINE, split
fields = ["Title", "Version", "Author", "Description", "License", "URL",
"BugReports", "Encoding", "RoxygenNote", "Depends", "Imports", "LinkingTo",
"Suggests", "Enhance", "Config/testthat/edition", "VignetteBuilder", "NeedsCompilation",
"Packaged", "Repository", "Date/Publication"]
fields_suggested = ["Title", "License", "Maintainer", "Maintaner",
"Depends", "Imports", "LinkingTo", "Suggests"]
base_github_raw = "https://raw.githubusercontent.com/cran"
base_github_api = "https://api.github.com/repos/cran"
av_cran = "https://cran.r-project.org/web/packages/available_packages_by_name.html"
def parse_dcf(string: str) -> dict:
"""
Parse a string like Debian Control file
Parameters:
string (str): a string to parse, dcf format
Returns:
dict: dcf formatted to key-value structure
"""
split_regex = compile(r"^[A-Za-z-@/]+:\s", flags=MULTILINE)
keys = [key[:-2].strip() for key in split_regex.findall(string)]
values = [value.strip() for value in split(split_regex, string)[1:]]
return dict(zip(keys, values))
def table_comparison(str1: str, str2: str, fields: list) -> TagList:
"""
Built a table from two dcf like formated strings
Parameters:
str1 (str): a string to parse, dcf format
str2 (str): a string to parse, dcf format
fields (list): a list with fields, please check R cran docs
Returns:
TagList: a html table with 3 columns
"""
str1_pre = str1.replace("\t", " ")
str2_pre = str2.replace("\t", " ")
dic1 = parse_dcf(str1_pre)
dic2 = parse_dcf(str2_pre)
res = list()
for k in fields:
res.append(
tr(
th(k),
td(str(dic1.get(k, None))),
td(str(dic2.get(k, None)))
)
)
return TagList(table(
thead(
tr(
th(""),
th("Version: " + dic1.get("Version", "1")),
th("Version: " + dic2.get("Version", "2"))
)
),
tbody(
res
)
).add_class("table table-striped"))
app_ui = ui.page_fluid(
ui.panel_title("Compare R DESCRIPTION files"),
head(style(".control-label {font-weight: bold;}")),
ui.page_sidebar(
ui.sidebar(
ui.input_text(
"pac",
span(a("CRAN", href = av_cran, class_="mx-2"), "Package:"),
"shiny"
),
ui.input_selectize(
"fields",
"Fields",
choices = fields,
selected = fields_suggested,
multiple = True
),
ui.input_select(
"version_old",
"Version 1:",
choices = [""]
),
ui.input_select(
"version_new",
"Version 2:",
choices = [""]
)
),
ui.card(
ui.output_ui("info")
)
)
)
def server(input, output, session):
@reactive.Calc
@reactive.event(input.version_old)
async def pac_github_old():
response = await pyodide.http.pyfetch(
base_github_raw +
f"/{input.pac()}/{input.version_old()}/DESCRIPTION"
)
if (response.status != 200):
raise Exception(f"Error fetching")
try:
data = await response.string()
except:
data = None
return data
@reactive.Calc
@reactive.event(input.version_new)
async def pac_github_new():
response = await pyodide.http.pyfetch(
base_github_raw +
f"/{input.pac()}/{input.version_new()}/DESCRIPTION"
)
if (response.status != 200):
raise Exception(f"Error fetching")
try:
data = await response.string()
except:
data = ""
return data
@reactive.Calc
@reactive.event(input.pac)
async def tags_data():
response = await pyodide.http.pyfetch(
base_github_api + f"/{input.pac()}/tags?per_page=500"
)
if (response.status == 200):
try:
data = await response.json()
except:
data = ""
else:
data = ""
return data
@reactive.Effect()
@reactive.event(input.pac)
async def versions():
tags_pac_raw = await tags_data()
tags_pac = [k["name"] for k in tags_pac_raw]
tags_pac = list(filter(lambda x: not x.startswith("R-"), tags_pac))
if len(tags_pac) >= 2:
ui.update_select(
"version_old",
choices = tags_pac,
selected = tags_pac[1]
),
ui.update_select(
"version_new",
choices = tags_pac,
selected = tags_pac[0]
)
else:
ui.update_select(
"version_old",
choices = [""]
),
ui.update_select(
"version_new",
choices = [""]
)
@output
@render.ui
async def info():
req(input.version_old())
req(input.version_new())
data_old = await pac_github_old()
data_new = await pac_github_new()
if (data_old != "") and (data_new != ""):
return div(
table_comparison(data_old, data_new, input.fields())
).add_class("row")
else:
return div("Failed")
app = App(app_ui, server)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment