///| Git-style text format serialization for PR data
///|
/// Serialize PullRequest to Git-style text format
pub fn PullRequest::serialize(self : PullRequest) -> String {
let sb = StringBuilder::new()
sb.write_string("pr ")
sb.write_string(self.id)
sb.write_char('\n')
sb.write_string("title ")
sb.write_string(self.title)
sb.write_char('\n')
sb.write_string("source ")
sb.write_string(self.source_branch)
sb.write_char('\n')
match self.source_repo {
Some(repo) => {
sb.write_string("source-repo ")
sb.write_string(repo)
sb.write_char('\n')
}
None => ()
}
match self.source_ref {
Some(source_ref) => {
sb.write_string("source-ref ")
sb.write_string(source_ref)
sb.write_char('\n')
}
None => ()
}
sb.write_string("source-commit ")
sb.write_string(self.source_commit.to_hex())
sb.write_char('\n')
sb.write_string("target ")
sb.write_string(self.target_branch)
sb.write_char('\n')
sb.write_string("target-commit ")
sb.write_string(self.target_commit.to_hex())
sb.write_char('\n')
sb.write_string("author ")
sb.write_string(self.author)
sb.write_char('\n')
sb.write_string("created ")
sb.write_string(self.created_at.to_string())
sb.write_char('\n')
sb.write_string("updated ")
sb.write_string(self.updated_at.to_string())
sb.write_char('\n')
sb.write_string("state ")
sb.write_string(self.state.to_string())
sb.write_char('\n')
for label in self.labels {
sb.write_string("label ")
sb.write_string(label)
sb.write_char('\n')
}
for issue_id in self.closes_issues {
sb.write_string("closes ")
sb.write_string(issue_id)
sb.write_char('\n')
}
match self.merge_commit {
Some(mc) => {
sb.write_string("merge-commit ")
sb.write_string(mc.to_hex())
sb.write_char('\n')
}
None => ()
}
sb.write_char('\n')
sb.write_string(self.body)
sb.to_string()
}
///|
/// Parse legacy PullRequest payload (`pr ...`) from Git-style text format
pub fn parse_legacy_pull_request(text : String) -> PullRequest raise PrError {
let mut id = ""
let mut title = ""
let mut source_branch = ""
let mut source_repo : String? = None
let mut source_ref : String? = None
let mut source_commit_hex = ""
let mut target_branch = ""
let mut target_commit_hex = ""
let mut author = ""
let mut created_at : Int64 = 0
let mut updated_at : Int64 = 0
let mut state = PrState::Open
let labels : Array[String] = []
let closes_issues : Array[String] = []
let mut merge_commit : @bit.ObjectId? = None
let body_lines : Array[String] = []
let mut in_body = false
for line_view in text.split("\n") {
let line = line_view.to_owned()
if in_body {
body_lines.push(line)
continue
}
if line.length() == 0 {
in_body = true
continue
}
let space = line.find(" ")
match space {
None => continue
Some(idx) => {
let key = String::unsafe_substring(line, start=0, end=idx)
let value = String::unsafe_substring(
line,
start=idx + 1,
end=line.length(),
)
match key {
"pr" => id = value
"title" => title = value
"source" => source_branch = value
"source-repo" => source_repo = Some(value)
"source-ref" => source_ref = Some(value)
"source-commit" => source_commit_hex = value
"target" => target_branch = value
"target-commit" => target_commit_hex = value
"author" => author = value
"created" => created_at = parse_int64(value)
"updated" => updated_at = parse_int64(value)
"state" => state = parse_pr_state(value)
"label" => labels.push(value)
"closes" => closes_issues.push(value)
"merge-commit" =>
merge_commit = Some(
@bit.ObjectId::from_hex(value) catch {
_ => raise PrError::InvalidFormat("Invalid merge-commit hex")
},
)
_ => ()
}
}
}
}
if id.length() == 0 {
raise PrError::InvalidFormat("Missing pr id")
}
let source_commit = @bit.ObjectId::from_hex(source_commit_hex) catch {
_ => raise PrError::InvalidFormat("Invalid source-commit hex")
}
let target_commit = @bit.ObjectId::from_hex(target_commit_hex) catch {
_ => raise PrError::InvalidFormat("Invalid target-commit hex")
}
PullRequest::new(
id,
title,
body_lines.join("\n"),
source_branch,
source_commit,
target_branch,
target_commit,
author,
created_at,
updated_at,
state,
labels,
closes_issues~,
merge_commit~,
source_repo~,
source_ref~,
)
}
///|
/// Serialize WorkItem to Git-style text format
pub fn WorkItem::serialize(self : WorkItem) -> String {
let sb = StringBuilder::new()
sb.write_string("work-item ")
sb.write_string(self.id)
sb.write_char('\n')
sb.write_string("title ")
sb.write_string(self.title)
sb.write_char('\n')
sb.write_string("author ")
sb.write_string(self.author)
sb.write_char('\n')
sb.write_string("created ")
sb.write_string(self.created_at.to_string())
sb.write_char('\n')
sb.write_string("updated ")
sb.write_string(self.updated_at.to_string())
sb.write_char('\n')
sb.write_string("state ")
sb.write_string(self.state.to_string())
sb.write_char('\n')
for label in self.labels {
sb.write_string("label ")
sb.write_string(label)
sb.write_char('\n')
}
for assignee in self.assignees {
sb.write_string("assignee ")
sb.write_string(assignee)
sb.write_char('\n')
}
for pr_id in self.linked_prs {
sb.write_string("linked-pr ")
sb.write_string(pr_id)
sb.write_char('\n')
}
for issue_ref in self.linked_issues {
sb.write_string("linked-issue ")
sb.write_string(issue_ref)
sb.write_char('\n')
}
match self.parent_id {
Some(pid) => {
sb.write_string("parent ")
sb.write_string(pid)
sb.write_char('\n')
}
None => ()
}
for id in self.blocked_by {
sb.write_string("blocked-by ")
sb.write_string(id)
sb.write_char('\n')
}
for id in self.blocking {
sb.write_string("blocking ")
sb.write_string(id)
sb.write_char('\n')
}
match self.patch {
None => ()
Some(patch) => {
sb.write_string("source ")
sb.write_string(patch.source_branch)
sb.write_char('\n')
match patch.source_repo {
Some(repo) => {
sb.write_string("source-repo ")
sb.write_string(repo)
sb.write_char('\n')
}
None => ()
}
match patch.source_ref {
Some(source_ref) => {
sb.write_string("source-ref ")
sb.write_string(source_ref)
sb.write_char('\n')
}
None => ()
}
sb.write_string("source-commit ")
sb.write_string(patch.source_commit.to_hex())
sb.write_char('\n')
sb.write_string("target ")
sb.write_string(patch.target_branch)
sb.write_char('\n')
sb.write_string("target-commit ")
sb.write_string(patch.target_commit.to_hex())
sb.write_char('\n')
for issue_id in patch.closes_issues {
sb.write_string("closes ")
sb.write_string(issue_id)
sb.write_char('\n')
}
match patch.merge_commit {
Some(mc) => {
sb.write_string("merge-commit ")
sb.write_string(mc.to_hex())
sb.write_char('\n')
}
None => ()
}
}
}
sb.write_char('\n')
sb.write_string(self.body)
sb.to_string()
}
///|
/// Parse WorkItem from Git-style text format
pub fn parse_work_item(text : String) -> WorkItem raise PrError {
let mut id = ""
let mut title = ""
let mut author = ""
let mut created_at : Int64 = 0
let mut updated_at : Int64 = 0
let mut state = WorkItemState::Open
let labels : Array[String] = []
let assignees : Array[String] = []
let linked_prs : Array[String] = []
let linked_issues : Array[String] = []
let mut source_branch : String? = None
let mut source_repo : String? = None
let mut source_ref : String? = None
let mut source_commit_hex : String? = None
let mut target_branch : String? = None
let mut target_commit_hex : String? = None
let closes_issues : Array[String] = []
let mut merge_commit : @bit.ObjectId? = None
let mut parent_id : String? = None
let blocked_by : Array[String] = []
let blocking : Array[String] = []
let body_lines : Array[String] = []
let mut in_body = false
for line_view in text.split("\n") {
let line = line_view.to_owned()
if in_body {
body_lines.push(line)
continue
}
if line.length() == 0 {
in_body = true
continue
}
let space = line.find(" ")
match space {
None => continue
Some(idx) => {
let key = String::unsafe_substring(line, start=0, end=idx)
let value = String::unsafe_substring(
line,
start=idx + 1,
end=line.length(),
)
match key {
"work-item" => id = value
"title" => title = value
"author" => author = value
"created" => created_at = parse_int64(value)
"updated" => updated_at = parse_int64(value)
"state" => state = parse_work_item_state(value)
"label" => labels.push(value)
"assignee" => assignees.push(value)
"linked-pr" => linked_prs.push(value)
"linked-issue" => linked_issues.push(value)
"parent" => parent_id = Some(value)
"blocked-by" => blocked_by.push(value)
"blocking" => blocking.push(value)
"source" => source_branch = Some(value)
"source-repo" => source_repo = Some(value)
"source-ref" => source_ref = Some(value)
"source-commit" => source_commit_hex = Some(value)
"target" => target_branch = Some(value)
"target-commit" => target_commit_hex = Some(value)
"closes" => closes_issues.push(value)
"merge-commit" =>
merge_commit = Some(
@bit.ObjectId::from_hex(value) catch {
_ => raise PrError::InvalidFormat("Invalid merge-commit hex")
},
)
_ => ()
}
}
}
}
if id.length() == 0 {
raise PrError::InvalidFormat("Missing work item id")
}
let patch = match
(source_branch, source_commit_hex, target_branch, target_commit_hex) {
(None, None, None, None) => None
(Some(src_branch), Some(src_hex), Some(tgt_branch), Some(tgt_hex)) => {
let source_commit = @bit.ObjectId::from_hex(src_hex) catch {
_ => raise PrError::InvalidFormat("Invalid source-commit hex")
}
let target_commit = @bit.ObjectId::from_hex(tgt_hex) catch {
_ => raise PrError::InvalidFormat("Invalid target-commit hex")
}
Some(
WorkItemPatch::new(
src_branch,
source_commit,
tgt_branch,
target_commit,
closes_issues~,
merge_commit~,
source_repo~,
source_ref~,
),
)
}
_ => raise PrError::InvalidFormat("Incomplete work item patch fields")
}
WorkItem::new(
id,
title,
body_lines.join("\n"),
author,
created_at,
updated_at,
state,
labels~,
assignees~,
linked_prs~,
linked_issues~,
patch~,
parent_id~,
blocked_by~,
blocking~,
)
}
///|
/// Serialize PrComment to Git-style text format
pub fn PrComment::serialize(self : PrComment) -> String {
let sb = StringBuilder::new()
sb.write_string("comment ")
sb.write_string(self.id)
sb.write_char('\n')
sb.write_string("pr ")
sb.write_string(self.pr_id)
sb.write_char('\n')
sb.write_string("author ")
sb.write_string(self.author)
sb.write_char('\n')
sb.write_string("created ")
sb.write_string(self.created_at.to_string())
sb.write_char('\n')
match self.reply_to {
Some(r) => {
sb.write_string("reply-to ")
sb.write_string(r)
sb.write_char('\n')
}
None => ()
}
match self.file_path {
Some(p) => {
sb.write_string("file ")
sb.write_string(p)
sb.write_char('\n')
}
None => ()
}
match self.line_number {
Some(n) => {
sb.write_string("line ")
sb.write_string(n.to_string())
sb.write_char('\n')
}
None => ()
}
match self.commit_id {
Some(c) => {
sb.write_string("commit ")
sb.write_string(c.to_hex())
sb.write_char('\n')
}
None => ()
}
sb.write_char('\n')
sb.write_string(self.body)
sb.to_string()
}
///|
/// Parse PrComment from Git-style text format
pub fn parse_pr_comment(text : String) -> PrComment raise PrError {
let mut id = ""
let mut pr_id = ""
let mut author = ""
let mut created_at : Int64 = 0
let mut reply_to : String? = None
let mut file_path : String? = None
let mut line_number : Int? = None
let mut commit_id : @bit.ObjectId? = None
let body_lines : Array[String] = []
let mut in_body = false
for line_view in text.split("\n") {
let line = line_view.to_owned()
if in_body {
body_lines.push(line)
continue
}
if line.length() == 0 {
in_body = true
continue
}
let space = line.find(" ")
match space {
None => continue
Some(idx) => {
let key = String::unsafe_substring(line, start=0, end=idx)
let value = String::unsafe_substring(
line,
start=idx + 1,
end=line.length(),
)
match key {
"comment" => id = value
"pr" => pr_id = value
"author" => author = value
"created" => created_at = parse_int64(value)
"reply-to" => reply_to = Some(value)
"file" => file_path = Some(value)
"line" => line_number = value |> parse_int |> Some
"commit" =>
commit_id = Some(
@bit.ObjectId::from_hex(value) catch {
_ => raise PrError::InvalidFormat("Invalid commit hex")
},
)
_ => ()
}
}
}
}
if id.length() == 0 {
raise PrError::InvalidFormat("Missing comment id")
}
PrComment::new(
id,
pr_id,
author,
body_lines.join("\n"),
created_at,
reply_to~,
file_path~,
line_number~,
commit_id~,
)
}
///|
/// Serialize PrReview to Git-style text format
pub fn PrReview::serialize(self : PrReview) -> String {
let sb = StringBuilder::new()
sb.write_string("review ")
sb.write_string(self.id)
sb.write_char('\n')
sb.write_string("pr ")
sb.write_string(self.pr_id)
sb.write_char('\n')
sb.write_string("author ")
sb.write_string(self.author)
sb.write_char('\n')
sb.write_string("verdict ")
sb.write_string(self.verdict.to_string())
sb.write_char('\n')
sb.write_string("created ")
sb.write_string(self.created_at.to_string())
sb.write_char('\n')
sb.write_string("commit ")
sb.write_string(self.commit_id.to_hex())
sb.write_char('\n')
sb.write_char('\n')
sb.write_string(self.body)
sb.to_string()
}
///|
/// Parse PrReview from Git-style text format
pub fn parse_pr_review(text : String) -> PrReview raise PrError {
let mut id = ""
let mut pr_id = ""
let mut author = ""
let mut verdict = ReviewVerdict::Comment
let mut created_at : Int64 = 0
let mut commit_hex = ""
let body_lines : Array[String] = []
let mut in_body = false
for line_view in text.split("\n") {
let line = line_view.to_owned()
if in_body {
body_lines.push(line)
continue
}
if line.length() == 0 {
in_body = true
continue
}
let space = line.find(" ")
match space {
None => continue
Some(idx) => {
let key = String::unsafe_substring(line, start=0, end=idx)
let value = String::unsafe_substring(
line,
start=idx + 1,
end=line.length(),
)
match key {
"review" => id = value
"pr" => pr_id = value
"author" => author = value
"verdict" => verdict = parse_review_verdict(value)
"created" => created_at = parse_int64(value)
"commit" => commit_hex = value
_ => ()
}
}
}
}
if id.length() == 0 {
raise PrError::InvalidFormat("Missing review id")
}
let commit_id = @bit.ObjectId::from_hex(commit_hex) catch {
_ => raise PrError::InvalidFormat("Invalid commit hex")
}
PrReview::new(
id,
pr_id,
author,
verdict,
body_lines.join("\n"),
created_at,
commit_id,
)
}
///|
fn parse_int64(s : String) -> Int64 {
let mut result : Int64 = 0
for c in s {
if c >= '0' && c <= '9' {
result = result * 10 + (c.to_int() - '0'.to_int()).to_int64()
}
}
result
}
///|
fn parse_int(s : String) -> Int {
let mut result = 0
for c in s {
if c >= '0' && c <= '9' {
result = result * 10 + (c.to_int() - '0'.to_int())
}
}
result
}
///|
fn parse_pr_state(s : String) -> PrState {
match s {
"open" => PrState::Open
"merged" => PrState::Merged
"closed" => PrState::Closed
_ => PrState::Open
}
}
///|
fn parse_work_item_state(s : String) -> WorkItemState {
match s {
"open" => WorkItemState::Open
"merged" => WorkItemState::Merged
"closed" => WorkItemState::Closed
_ => WorkItemState::Open
}
}
///|
fn parse_review_verdict(s : String) -> ReviewVerdict {
match s {
"approved" => ReviewVerdict::Approved
"request-changes" => ReviewVerdict::RequestChanges
"comment" => ReviewVerdict::Comment
_ => ReviewVerdict::Comment
}
}
///|
/// Serialize Issue to Git-style text format
pub fn Issue::serialize(self : Issue) -> String {
let sb = StringBuilder::new()
sb.write_string("issue ")
sb.write_string(self.id)
sb.write_char('\n')
sb.write_string("title ")
sb.write_string(self.title)
sb.write_char('\n')
sb.write_string("author ")
sb.write_string(self.author)
sb.write_char('\n')
sb.write_string("created ")
sb.write_string(self.created_at.to_string())
sb.write_char('\n')
sb.write_string("updated ")
sb.write_string(self.updated_at.to_string())
sb.write_char('\n')
sb.write_string("state ")
sb.write_string(self.state.to_string())
sb.write_char('\n')
for label in self.labels {
sb.write_string("label ")
sb.write_string(label)
sb.write_char('\n')
}
for assignee in self.assignees {
sb.write_string("assignee ")
sb.write_string(assignee)
sb.write_char('\n')
}
for pr_id in self.linked_prs {
sb.write_string("linked-pr ")
sb.write_string(pr_id)
sb.write_char('\n')
}
for issue_ref in self.linked_issues {
sb.write_string("linked-issue ")
sb.write_string(issue_ref)
sb.write_char('\n')
}
match self.parent_id {
Some(pid) => {
sb.write_string("parent ")
sb.write_string(pid)
sb.write_char('\n')
}
None => ()
}
for id in self.blocked_by {
sb.write_string("blocked-by ")
sb.write_string(id)
sb.write_char('\n')
}
for id in self.blocking {
sb.write_string("blocking ")
sb.write_string(id)
sb.write_char('\n')
}
sb.write_char('\n')
sb.write_string(self.body)
sb.to_string()
}
///|
/// Parse legacy Issue payload (`issue ...`) from Git-style text format
pub fn parse_legacy_issue(text : String) -> Issue raise PrError {
let mut id = ""
let mut title = ""
let mut author = ""
let mut created_at : Int64 = 0
let mut updated_at : Int64 = 0
let mut state = IssueState::Open
let labels : Array[String] = []
let assignees : Array[String] = []
let linked_prs : Array[String] = []
let linked_issues : Array[String] = []
let mut parent_id : String? = None
let blocked_by : Array[String] = []
let blocking : Array[String] = []
let body_lines : Array[String] = []
let mut in_body = false
for line_view in text.split("\n") {
let line = line_view.to_owned()
if in_body {
body_lines.push(line)
continue
}
if line.length() == 0 {
in_body = true
continue
}
let space = line.find(" ")
match space {
None => continue
Some(idx) => {
let key = String::unsafe_substring(line, start=0, end=idx)
let value = String::unsafe_substring(
line,
start=idx + 1,
end=line.length(),
)
match key {
"issue" => id = value
"title" => title = value
"author" => author = value
"created" => created_at = parse_int64(value)
"updated" => updated_at = parse_int64(value)
"state" => state = parse_issue_state(value)
"label" => labels.push(value)
"assignee" => assignees.push(value)
"linked-pr" => linked_prs.push(value)
"linked-issue" => linked_issues.push(value)
"parent" => parent_id = Some(value)
"blocked-by" => blocked_by.push(value)
"blocking" => blocking.push(value)
_ => ()
}
}
}
}
if id.length() == 0 {
raise PrError::InvalidFormat("Missing issue id")
}
Issue::new(
id,
title,
body_lines.join("\n"),
author,
created_at,
updated_at,
state,
labels~,
assignees~,
linked_prs~,
linked_issues~,
parent_id~,
blocked_by~,
blocking~,
)
}
///|
/// Serialize IssueComment to Git-style text format
pub fn IssueComment::serialize(self : IssueComment) -> String {
let sb = StringBuilder::new()
sb.write_string("comment ")
sb.write_string(self.id)
sb.write_char('\n')
sb.write_string("issue ")
sb.write_string(self.issue_id)
sb.write_char('\n')
sb.write_string("author ")
sb.write_string(self.author)
sb.write_char('\n')
sb.write_string("created ")
sb.write_string(self.created_at.to_string())
sb.write_char('\n')
match self.reply_to {
Some(r) => {
sb.write_string("reply-to ")
sb.write_string(r)
sb.write_char('\n')
}
None => ()
}
sb.write_char('\n')
sb.write_string(self.body)
sb.to_string()
}
///|
/// Parse IssueComment from Git-style text format
pub fn parse_issue_comment(text : String) -> IssueComment raise PrError {
let mut id = ""
let mut issue_id = ""
let mut author = ""
let mut created_at : Int64 = 0
let mut reply_to : String? = None
let body_lines : Array[String] = []
let mut in_body = false
for line_view in text.split("\n") {
let line = line_view.to_owned()
if in_body {
body_lines.push(line)
continue
}
if line.length() == 0 {
in_body = true
continue
}
let space = line.find(" ")
match space {
None => continue
Some(idx) => {
let key = String::unsafe_substring(line, start=0, end=idx)
let value = String::unsafe_substring(
line,
start=idx + 1,
end=line.length(),
)
match key {
"comment" => id = value
"issue" => issue_id = value
"author" => author = value
"created" => created_at = parse_int64(value)
"reply-to" => reply_to = Some(value)
_ => ()
}
}
}
}
if id.length() == 0 {
raise PrError::InvalidFormat("Missing comment id")
}
IssueComment::new(
id,
issue_id,
author,
body_lines.join("\n"),
created_at,
reply_to~,
)
}
///|
fn parse_issue_state(s : String) -> IssueState {
match s {
"open" => IssueState::Open
"closed" => IssueState::Closed
_ => IssueState::Open
}
}
///|
/// Serialize Note to Git-style text format
/// Note: Git standard notes are plain text, but we add metadata for Hub
pub fn Note::serialize(self : Note) -> String {
let sb = StringBuilder::new()
sb.write_string("commit ")
sb.write_string(self.commit_id.to_hex())
sb.write_char('\n')
sb.write_string("author ")
sb.write_string(self.author)
sb.write_char('\n')
sb.write_string("created ")
sb.write_string(self.created_at.to_string())
sb.write_char('\n')
sb.write_string("ns ")
sb.write_string(self.ns)
sb.write_char('\n')
sb.write_char('\n')
sb.write_string(self.body)
sb.to_string()
}
///|
/// Serialize Note as plain text (Git compatible, no metadata)
pub fn Note::serialize_plain(self : Note) -> String {
self.body
}
///|
/// Parse Note from Git-style text format (with metadata)
pub fn parse_note(text : String) -> Note {
let mut commit_id = @bit.ObjectId::zero()
let mut author = ""
let mut created_at : Int64 = 0
let mut ns = "commits"
let body_lines : Array[String] = []
let mut in_body = false
for line_view in text.split("\n") {
let line = line_view.to_owned()
if in_body {
body_lines.push(line)
continue
}
if line.length() == 0 {
in_body = true
continue
}
let space = line.find(" ")
match space {
None => continue
Some(idx) => {
let key = String::unsafe_substring(line, start=0, end=idx)
let value = String::unsafe_substring(
line,
start=idx + 1,
end=line.length(),
)
match key {
"commit" =>
commit_id = @bit.ObjectId::from_hex(value) catch {
_ => @bit.ObjectId::zero()
}
"author" => author = value
"created" => created_at = parse_int64(value)
"ns" => ns = value
_ => ()
}
}
}
}
Note::new(commit_id, body_lines.join("\n"), author, created_at, ns~)
}
///|
/// Parse Note from plain text (Git compatible, creates minimal Note)
pub fn parse_note_plain(commit_id : @bit.ObjectId, text : String) -> Note {
Note::new(commit_id, text, "", 0L, ns="commits")
}