Last active
February 14, 2025 13:52
-
-
Save houstonhaynes/384b035490361508f8a2646640aa7f1b to your computer and use it in GitHub Desktop.
An F# script to extract open GitHub issues from a repo as Markdown
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
#r "nuget: Octokit" | |
open Octokit | |
open System | |
open System.IO | |
open System.Text.RegularExpressions | |
// Configuration | |
type Config = { | |
Token: string | |
RepoUrl: string | |
OutputDir: string | |
} | |
// URL parsing | |
let parseGitHubUrl (url: string) = | |
let pattern = @"github\.com/([^/]+)/([^/]+)" | |
let m = Regex.Match(url, pattern) | |
if m.Success then | |
Some(m.Groups.[1].Value, m.Groups.[2].Value) | |
else | |
None | |
// Helper functions to format markdown | |
let formatUser (user: User) = | |
sprintf "[@%s](https://github.com/%s)" user.Login user.Login | |
let formatDateTime (dt: DateTimeOffset) = | |
dt.ToString("yyyy-MM-dd HH:mm:ss UTC") | |
let formatLabels (labels: Label seq) = | |
labels | |
|> Seq.map (fun label -> sprintf "`%s`" label.Name) | |
|> String.concat " " | |
let formatBody (body: string) = | |
if String.IsNullOrEmpty(body) then "" | |
else sprintf "\n\n%s" body | |
// Format a single issue as markdown | |
let formatIssue (issue: Issue) (comments: IssueComment seq) = | |
let header = sprintf "# %s\n\n" issue.Title | |
let metadata = | |
sprintf "**Issue #%d** | Created by %s | %s | %s\n\n" | |
issue.Number | |
(formatUser issue.User) | |
(formatDateTime issue.CreatedAt) | |
(formatLabels issue.Labels) | |
let issueBody = formatBody issue.Body | |
let comments = | |
comments | |
|> Seq.map (fun comment -> | |
sprintf "\n\n---\n\n**Comment by %s** | %s%s" | |
(formatUser comment.User) | |
(formatDateTime comment.CreatedAt) | |
(formatBody comment.Body)) | |
|> String.concat "" | |
let state = | |
sprintf "\n\n---\n\n**Status**: %s" | |
(if issue.State.Value = ItemState.Closed then | |
sprintf "Closed on %s" (formatDateTime issue.ClosedAt.Value) | |
else "Open") | |
header + metadata + issueBody + comments + state | |
// Main function to extract issues | |
let extractIssues (config: Config) = async { | |
match parseGitHubUrl config.RepoUrl with | |
| None -> | |
printfn "Invalid GitHub URL. Please provide a URL in the format: https://github.com/owner/repo" | |
return Error "Invalid URL" | |
| Some (owner, repo) -> | |
try | |
// Initialize GitHub client | |
let client = new GitHubClient(new ProductHeaderValue("IssueExtractor")) | |
client.Credentials <- Credentials(config.Token) | |
// Get open issues | |
let! issues = | |
client.Issue.GetAllForRepository( | |
owner, | |
repo, | |
new RepositoryIssueRequest(State = ItemStateFilter.Open)) | |
|> Async.AwaitTask | |
// Create output directory if it doesn't exist | |
Directory.CreateDirectory(config.OutputDir) |> ignore | |
// Process each issue | |
for issue in issues do | |
// Get comments for the issue | |
let! comments = | |
client.Issue.Comment.GetAllForIssue( | |
owner, | |
repo, | |
issue.Number) | |
|> Async.AwaitTask | |
// Format issue and comments as markdown | |
let markdown = formatIssue issue comments | |
// Save to file | |
let filename = | |
Path.Combine( | |
config.OutputDir, | |
sprintf "issue-%d.md" issue.Number) | |
File.WriteAllText(filename, markdown) | |
printfn "Processed issue #%d" issue.Number | |
// Add small delay to avoid rate limiting | |
do! Async.Sleep 1000 | |
return Ok() | |
with ex -> | |
printfn "Error: %s" ex.Message | |
return Error ex.Message | |
} | |
// Example usage | |
let config = { | |
Token = "[YOUR_GITHUB_TOKEN_HERE]" // Replace with your GitHub token | |
RepoUrl = "https://github.com/account/project" // Just paste the full GitHub repo URL here | |
OutputDir = "issues" | |
} | |
// Run the extractor | |
match Async.RunSynchronously (extractIssues config) with | |
| Ok () -> printfn "Successfully extracted all open issues" | |
| Error msg -> printfn "Failed to extract issues: %s" msg |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment