Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/r.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ on:
- main
paths:
- "R/**"
- "tests/**"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Trigger validation when the bundled font changes.

The test suite depends on assets/FiraCode.ttf, but this path filter covers R and test files only. Add assets/** so a removed or invalid font runs CI.

Proposed fix
       - "tests/**"
+      - "assets/**"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- "tests/**"
- "tests/**"
- "assets/**"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/r.yml at line 36, Update the path filters in the workflow
so changes under assets/**, including assets/FiraCode.ttf, trigger the test
suite alongside the existing R and tests/** patterns.

- ".github/workflows/r.yml"

jobs:
Expand Down Expand Up @@ -78,14 +79,17 @@ jobs:
- name: Install R dependencies
run: |
Rscript -e '
packages <- c("httr", "jsonlite", "dplyr", "magrittr", "showtext", "ggplot2", "treemapify", "svglite")
packages <- c("httr", "jsonlite", "dplyr", "showtext", "sysfonts", "ggplot2", "treemapify", "svglite")
new_packages <- packages[!(packages %in% installed.packages()[,"Package"])]
if(length(new_packages)) {
install.packages(new_packages, repos="https://cloud.r-project.org", dependencies=TRUE)
install.packages(new_packages, repos="https://cloud.r-project.org", dependencies=NA)
}
'
shell: bash

- name: Run tests
run: Rscript tests/run_tests.R

- name: Run analysis
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
25 changes: 7 additions & 18 deletions R/fetch.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,6 @@
# Last Modified: 2025-10-25 17:25:34
##

# Load required packages
if (!requireNamespace("httr", quietly = TRUE)) {
install.packages("httr")
}
library(httr)

if (!requireNamespace("jsonlite", quietly = TRUE)) {
install.packages("jsonlite")
}
library(jsonlite)

#' Fetch GitHub Repository Data
#'
#' Fetches repository information for a GitHub user via the GitHub API.
Expand Down Expand Up @@ -65,20 +54,20 @@ fetch_json <- function(username, proxy = NULL) {
}

# Make HTTP request
response <- GET(
response <- httr::GET(
url,
add_headers(.headers = headers),
if (!is.null(proxy)) use_proxy(proxy)
httr::add_headers(.headers = headers),
if (!is.null(proxy)) httr::use_proxy(proxy)
)

# Check response status
if (status_code(response) != 200) {
stop("HTTP request failed with status code: ", status_code(response))
if (httr::status_code(response) != 200) {
stop("HTTP request failed with status code: ", httr::status_code(response))
}

# Parse JSON response
content_text <- content(response, "text", encoding = "UTF-8")
return(fromJSON(content_text))
content_text <- httr::content(response, "text", encoding = "UTF-8")
return(jsonlite::fromJSON(content_text))
},
error = function(e) {
message("Error fetching JSON data: ", e$message)
Expand Down
5 changes: 2 additions & 3 deletions R/fmt.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
# Last Modified: 2025-10-25 23:57:46
##

if (!require("styler", quietly = TRUE)) {
install.packages("styler")
library(styler)
if (!requireNamespace("styler", quietly = TRUE)) {
stop("Missing required package: styler. Install it with install.packages().")
}

#' Format R Code Files
Expand Down
20 changes: 20 additions & 0 deletions R/helpers.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
setup_visualization_font <- function(font_path = file.path("assets", "FiraCode.ttf")) {
font_family <- "sans"

if (!file.exists(font_path)) {
warning("Custom font not found at ", font_path, "; using default font.")
return(font_family)
}

tryCatch(
{
sysfonts::font_add("FiraCode", regular = font_path)
showtext::showtext_auto()
"FiraCode"
},
error = function(error) {
warning("Failed to load custom font, using default: ", error$message)
font_family
}
)
}
59 changes: 25 additions & 34 deletions R/main.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,31 @@
# Last Modified: 2025-10-25 16:18:16
##

# Load required packages
if (!require("jsonlite", quietly = TRUE)) {
install.packages("jsonlite")
library(jsonlite)
required_packages <- c(
"dplyr", "ggplot2", "httr", "jsonlite", "showtext", "svglite", "sysfonts",
"treemapify"
)
missing_packages <- required_packages[
!vapply(required_packages, requireNamespace, logical(1), quietly = TRUE)
]
if (length(missing_packages) > 0) {
stop(
"Missing required packages: ", paste(missing_packages, collapse = ", "),
". Install them with install.packages()."
)
}

source_module <- function(path) {
tryCatch(
suppressMessages(source(path)),
error = function(error) stop("Failed to source ", path, ": ", error$message)
)
}

# Source helper functions
tryCatch(
suppressMessages(source("R/fetch.R")),
error = function(e) stop("Failed to source R/fetch.R: ", e$message)
)

tryCatch(
suppressMessages(source("R/plot.R")),
error = function(e) stop("Failed to source R/plot.R: ", e$message)
)
invisible(lapply(
c("R/helpers.R", "R/fetch.R", "R/plot.R", "R/pie.R", "R/treemap.R"),
source_module
))

# Get GitHub username from environment or use default
username <- Sys.getenv("GITHUB_USERNAME", unset = "mingcheng")
Expand All @@ -50,12 +58,12 @@ plotted_file_name <- file.path(dir_name, paste0("plotted_", today, ".csv"))

# Fetch or load cached repository data
if (file.exists(raw_file_name)) {
json_data <- fromJSON(raw_file_name)
json_data <- jsonlite::fromJSON(raw_file_name)
message("Loaded cached data from ", raw_file_name)
} else {
# Check for HTTP proxy settings
proxy <- Sys.getenv("HTTP_PROXY", unset = "")
if (is.null(proxy) || proxy == "") {
if (!nzchar(proxy)) {
proxy <- NULL
message("No proxy configured.")
} else {
Expand All @@ -68,7 +76,7 @@ if (file.exists(raw_file_name)) {
}
message("Fetched ", length(json_data), " repositories")

write_json(json_data, raw_file_name, pretty = TRUE, auto_unbox = TRUE)
jsonlite::write_json(json_data, raw_file_name, pretty = TRUE, auto_unbox = TRUE)
message("Raw data saved to ", raw_file_name)
}

Expand All @@ -83,25 +91,8 @@ message("Successfully processed repository data")
write.csv(repos, plotted_file_name, row.names = FALSE)
message("Statistical data saved to ", plotted_file_name)

# Generate visualizations

# Generate pie chart
tryCatch(
suppressMessages(source("R/pie.R")),
error = function(e) stop("Failed to source R/pie.R: ", e$message)
)

generate_pie_chart(username, repos, dir_name, json_data)

# Generate treemap
tryCatch(
suppressMessages(source("R/treemap.R")),
error = function(e) stop("Failed to source R/treemap.R: ", e$message)
)

generate_treemap(username, repos, dir_name, json_data)




message("Analysis complete!")
19 changes: 3 additions & 16 deletions R/pie.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,16 @@
# Last Modified: 2025-10-26 11:19:07
##

if (!require("showtext", quietly = TRUE)) {
install.packages("showtext")
library(showtext)
}

generate_pie_chart <- function(username, repos, dir_name, json_data) {
# Setup font for visualization
tryCatch(
font_add("FiraCode", regular = "../assets/FiraCode.ttf"),
error = function(e) {
warning("Failed to load custom font, using default: ", e$message)
}
)

# Enable showtext before opening graphics device
showtext_auto()
font_family <- setup_visualization_font()

# Define function to generate chart
generate_chart <- function(username, repos, colors, total_repos, date_str) {
par(
bg = "white",
mai = c(0.5, 0.5, 0.8, 0.5),
lwd = 1,
family = "FiraCode"
family = font_family
)

pie(
Expand All @@ -58,6 +44,7 @@ generate_pie_chart <- function(username, repos, dir_name, json_data) {
colors <- rainbow(nrow(repos))
total_repos <- nrow(json_data)
date_str <- format(Sys.Date(), "%Y-%m-%d")
today <- format(Sys.Date(), "%Y%m%d")

# Define chart files
chart_files <- list(
Expand Down
42 changes: 13 additions & 29 deletions R/plot.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,6 @@
# Last Modified: 2025-10-25 17:08:48
##

# Load required packages
if (!require("dplyr", quietly = TRUE)) {
install.packages("dplyr")
library(dplyr)
}

if (!require("magrittr", quietly = TRUE)) {
install.packages("magrittr")
library(magrittr)
}

if (!require("magrittr", quietly = TRUE)) {
install.packages("magrittr")
library(magrittr)
}

#' Process and Analyze Repository Data
#'
#' Calculate weighted language distribution scores based on repository
Expand All @@ -44,8 +28,8 @@ plot_repos <- function(repos_data) {
cat("\n")

# Calculate weighted scores and aggregate by language
repos_df <- repos_data %>%
mutate(
repos_df <- repos_data |>
dplyr::mutate(
# Calculate update recency weight (most important factor)
days_since_update = as.numeric(Sys.Date() - as.Date(updated_at)),
# Exponential decay: 1 year decay constant
Expand All @@ -56,36 +40,36 @@ plot_repos <- function(repos_data) {
# Weighted score: priority = update time > repo count > stars > forks
# Note: repo count is calculated after grouping
weighted_score = (star_score * 0.2 + fork_score * 0.1 + 1.5) * (date_weight^1.5)
) %>%
) |>
# Group by language and aggregate
group_by(language) %>%
summarise(
dplyr::group_by(language) |>
dplyr::summarise(
total_score = sum(weighted_score, na.rm = TRUE),
repo_count = n(),
repo_count = dplyr::n(),
total_stars = sum(stargazers_count, na.rm = TRUE),
total_forks = sum(forks_count, na.rm = TRUE),
avg_days_since_update = mean(days_since_update, na.rm = TRUE),
avg_date_weight = mean(date_weight, na.rm = TRUE),
.groups = "drop"
) %>%
) |>
# Filter out NA languages
filter(!is.na(language)) %>%
dplyr::filter(!is.na(language)) |>
# Add repository count weight (second most important factor)
mutate(
dplyr::mutate(
repo_count_score = log1p(repo_count) * 3.5,
# Final score = base score (with time weight) + repo count bonus
final_score = total_score + repo_count_score
) %>%
arrange(desc(final_score))
) |>
dplyr::arrange(dplyr::desc(final_score))

# Print statistical results
message("Language statistics:")
print(repos_df)
cat("\n")

# Calculate percentages and create labels
repos_df <- repos_df %>%
mutate(
repos_df <- repos_df |>
dplyr::mutate(
percentage = round(final_score / sum(final_score) * 100, 1),
label = paste0(language, " (", percentage, "%)")
)
Expand Down
Loading