Skip to content

Commit 739bfff

Browse files
authored
Include macro support (#291)
* Initial basic support for includes * Use path instead of name * Fix path trimming. Mark RFD as needs update on content update * Only do include processing when there is at least one include
1 parent 5048628 commit 739bfff

File tree

10 files changed

+627
-416
lines changed

10 files changed

+627
-416
lines changed

Cargo.lock

+358-365
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ hmac = "0.12.1"
3939
http = "1.2.0"
4040
hyper = "1.6.0"
4141
itertools = "0.13.0"
42-
jsonwebtoken = "9.3.0"
42+
jsonwebtoken = "9"
4343
meilisearch-sdk = "0.27.1"
4444
md-5 = "0.10.6"
4545
mime_guess = "2.0.5"
4646
mockall = "0.13.1"
4747
newline-converter = "0.3.0"
4848
newtype-uuid = { version = "1.2.1", features = ["schemars08", "serde", "v4"] }
4949
oauth2 = { version = "4.4.2", default-features = false, features = ["rustls-tls"] }
50-
octorust = "0.9.0"
50+
octorust = "0.10.0"
5151
owo-colors = "4.1.0"
5252
partial-struct = { git = "https://github.com/oxidecomputer/partial-struct" }
5353
progenitor = { git = "https://github.com/oxidecomputer/progenitor" }

rfd-data/src/content/asciidoc.rs

+132-30
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,40 @@ impl<'a> RfdAsciidoc<'a> {
157157
fn body(content: &str) -> Option<&str> {
158158
Self::title_pattern().splitn(content, 2).nth(1)
159159
}
160+
161+
fn include_pattern() -> Regex {
162+
Regex::new(r"(?m)^include::(.*)\[\]$").unwrap()
163+
}
164+
165+
pub fn includes(&'a self) -> Vec<AsciidocInclude<'a>> {
166+
Self::include_pattern()
167+
.captures_iter(&self.resolved)
168+
.map(|capture| {
169+
let extracted = capture.extract::<1>();
170+
AsciidocInclude {
171+
file: extracted.1[0],
172+
replacement: extracted.0,
173+
}
174+
})
175+
.collect::<Vec<_>>()
176+
}
177+
}
178+
179+
#[derive(Debug, PartialEq)]
180+
pub struct AsciidocInclude<'a> {
181+
file: &'a str,
182+
replacement: &'a str,
183+
}
184+
185+
impl<'a> AsciidocInclude<'a> {
186+
pub fn name(&self) -> &str {
187+
self.file
188+
}
189+
190+
pub fn perform_replacement(&self, body: &str, new_content: &str) -> String {
191+
tracing::trace!(self.replacement, "Replacing include");
192+
body.replace(self.replacement, new_content)
193+
}
160194
}
161195

162196
impl<'a> RfdDocument for RfdAsciidoc<'a> {
@@ -197,36 +231,6 @@ impl<'a> RfdDocument for RfdAsciidoc<'a> {
197231

198232
fn get_authors(&self) -> Option<&str> {
199233
Self::attr("authors", &self.resolved)
200-
// // If an authors attribute is defined anywhere in the document, then it is the first choice
201-
// // for the authors value
202-
// if let Some(attr) = Self::attr("authors", &self.resolved) {
203-
// Some(attr)
204-
// } else {
205-
// self.body().and_then(|body| {
206-
// body.lines().nth(0).and_then(|first_line| {
207-
// // If {authors} is found, instead search the header for an authors attribute
208-
// if first_line == "{authors}" {
209-
// Self::attr("authors", &self.resolved)
210-
// } else {
211-
// // Given that we are in a fallback case we need to be slightly picky on what
212-
// // lines we allow. We require that the line at least include a *@*.* word to
213-
// // try and filter out lines that are not actually author lines
214-
// let author_fallback_pattern =
215-
// Regex::new(r"^.*?([\S]+@[\S]+.[\S]+).*?$").unwrap();
216-
// let fallback_matches = author_fallback_pattern.is_match(first_line);
217-
218-
// if fallback_matches {
219-
// Some(first_line)
220-
// } else {
221-
// // If none of our attempts have found an author, we drop back to the
222-
// // attribute lookup. Eventually all of this logic should be removed and only
223-
// // the attribute version should be supported
224-
// Self::attr("authors", &self.resolved)
225-
// }
226-
// }
227-
// })
228-
// })
229-
// }
230234
}
231235

232236
fn get_labels(&self) -> Option<&str> {
@@ -970,4 +974,102 @@ This is the new body"#;
970974
rfd.update_body(&new_content).unwrap();
971975
assert_eq!(expected, rfd.raw());
972976
}
977+
978+
#[test]
979+
fn test_find_includes() {
980+
let contents = r#":reproducible:
981+
:showtitle:
982+
:toc: left
983+
:numbered:
984+
:icons: font
985+
:state: prediscussion
986+
:revremark: State: {state}
987+
:docdatetime: 2019-01-04 19:26:06 UTC
988+
:localdatetime: 2019-01-04 19:26:06 UTC
989+
:labels: label1, label2
990+
991+
= RFD 123 Place
992+
FirstName LastName <fname@company.org>
993+
994+
include::sub_doc1.adoc[]
995+
This is the new body
996+
997+
include::sub_doc2.adoc[]"#;
998+
let rfd = RfdAsciidoc::new(Cow::Borrowed(contents)).unwrap();
999+
let includes = rfd.includes();
1000+
1001+
let expected = vec![
1002+
AsciidocInclude {
1003+
file: "sub_doc1.adoc",
1004+
replacement: "include::sub_doc1.adoc[]",
1005+
},
1006+
AsciidocInclude {
1007+
file: "sub_doc2.adoc",
1008+
replacement: "include::sub_doc2.adoc[]",
1009+
},
1010+
];
1011+
assert_eq!(expected, includes);
1012+
}
1013+
1014+
#[test]
1015+
fn test_replace_include() {
1016+
let original = r#":reproducible:
1017+
:showtitle:
1018+
:toc: left
1019+
:numbered:
1020+
:icons: font
1021+
:state: prediscussion
1022+
:revremark: State: {state}
1023+
:docdatetime: 2019-01-04 19:26:06 UTC
1024+
:localdatetime: 2019-01-04 19:26:06 UTC
1025+
:labels: label1, label2
1026+
1027+
= RFD 123 Place
1028+
FirstName LastName <fname@company.org>
1029+
1030+
include::sub_doc1.adoc[]
1031+
This is the new body
1032+
1033+
include::sub_doc1.adoc[]
1034+
1035+
include::sub_doc2.adoc[]"#;
1036+
let include = AsciidocInclude {
1037+
file: "sub_doc1.adoc",
1038+
replacement: "include::sub_doc1.adoc[]",
1039+
};
1040+
1041+
let replacement_content = "Line 1
1042+
Line 2
1043+
Line 3";
1044+
1045+
let expected = r#":reproducible:
1046+
:showtitle:
1047+
:toc: left
1048+
:numbered:
1049+
:icons: font
1050+
:state: prediscussion
1051+
:revremark: State: {state}
1052+
:docdatetime: 2019-01-04 19:26:06 UTC
1053+
:localdatetime: 2019-01-04 19:26:06 UTC
1054+
:labels: label1, label2
1055+
1056+
= RFD 123 Place
1057+
FirstName LastName <fname@company.org>
1058+
1059+
Line 1
1060+
Line 2
1061+
Line 3
1062+
This is the new body
1063+
1064+
Line 1
1065+
Line 2
1066+
Line 3
1067+
1068+
include::sub_doc2.adoc[]"#;
1069+
1070+
assert_eq!(
1071+
expected,
1072+
include.perform_replacement(original, replacement_content)
1073+
);
1074+
}
9731075
}

rfd-github/src/ext.rs

+7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ impl ReposExt for Repos {
5454
pub trait ContentFileExt {
5555
fn is_empty(&self) -> bool;
5656
fn decode(&self) -> Result<Vec<u8>, DecodeError>;
57+
fn to_text(&self) -> Option<String>;
5758
}
5859

5960
impl ContentFileExt for ContentFile {
@@ -66,6 +67,12 @@ impl ContentFileExt for ContentFile {
6667
.decode(self.content.replace('\n', ""))
6768
.map(|data| data.trim().to_vec())
6869
}
70+
71+
fn to_text(&self) -> Option<String> {
72+
self.decode()
73+
.ok()
74+
.and_then(|content| String::from_utf8(content).ok())
75+
}
6976
}
7077

7178
trait SliceExt {

rfd-github/src/lib.rs

+16-7
Original file line numberDiff line numberDiff line change
@@ -541,18 +541,25 @@ impl GitHubRfdLocation {
541541
})
542542
}
543543

544-
/// Get a list of images that are store in this branch
545-
pub async fn get_images(
544+
/// Download the supporting documents that are within this RFD location
545+
pub async fn download_supporting_documents(
546546
&self,
547547
client: &Client,
548548
rfd_number: &RfdNumber,
549549
) -> Result<Vec<octorust::types::ContentFile>, GitHubError> {
550550
let dir = rfd_number.repo_path();
551-
Self::get_images_internal(client, &self.owner, &self.repo, &self.commit, dir).await
551+
Self::download_supporting_documents_internal(
552+
client,
553+
&self.owner,
554+
&self.repo,
555+
&self.commit,
556+
dir,
557+
)
558+
.await
552559
}
553560

554561
#[instrument(skip(client, dir))]
555-
fn get_images_internal<'a>(
562+
fn download_supporting_documents_internal<'a>(
556563
client: &'a Client,
557564
owner: &'a String,
558565
repo: &'a String,
@@ -575,13 +582,15 @@ impl GitHubRfdLocation {
575582
tracing::info!(file.path, file.type_, "Processing git entry");
576583

577584
if file.type_ == "dir" {
578-
let images =
579-
Self::get_images_internal(client, owner, repo, ref_, file.path).await?;
585+
let images = Self::download_supporting_documents_internal(
586+
client, owner, repo, ref_, file.path,
587+
)
588+
.await?;
580589

581590
for image in images {
582591
files.push(image)
583592
}
584-
} else if is_image(&file.name) {
593+
} else {
585594
let file = client
586595
.repos()
587596
.get_content_blob(owner, repo, ref_.0.as_str(), &file.path)

rfd-processor/src/content/mod.rs

+14-11
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub enum RenderableRfdError {
4848

4949
#[derive(Debug, Clone)]
5050
pub struct RenderableRfd<'a> {
51-
content: RfdContent<'a>,
51+
pub content: RfdContent<'a>,
5252
render_id: Uuid,
5353
}
5454

@@ -110,7 +110,8 @@ impl<'a> RenderableRfd<'a> {
110110
) -> Result<RfdPdf, RfdOutputError> {
111111
match &self.content {
112112
RfdContent::Asciidoc(adoc) => {
113-
self.download_images(client, number, branch).await?;
113+
self.download_supporting_documents(client, number, branch)
114+
.await?;
114115

115116
let pdf = RenderedPdf::render(adoc, self.tmp_path()?).await?;
116117

@@ -127,9 +128,9 @@ impl<'a> RenderableRfd<'a> {
127128
}
128129

129130
/// Downloads images that are stored on the provided GitHub branch for the given RFD number.
130-
/// These are stored locally so in a tmp directory for use by asciidoctor
131+
/// These are stored locally in a tmp directory for use by asciidoctor
131132
#[instrument(skip(self, client), fields(storage_path = ?self.tmp_path()))]
132-
async fn download_images(
133+
async fn download_supporting_documents(
133134
&self,
134135
client: &Client,
135136
number: &RfdNumber,
@@ -138,20 +139,22 @@ impl<'a> RenderableRfd<'a> {
138139
let dir = number.repo_path();
139140
let storage_path = self.tmp_path()?;
140141

141-
let images = location.get_images(client, number).await?;
142+
let documents = location
143+
.download_supporting_documents(client, number)
144+
.await?;
142145

143-
for image in images {
144-
let image_path = storage_path.join(
145-
image
146+
for document in documents {
147+
let document_path = storage_path.join(
148+
document
146149
.path
147150
.replace(dir.trim_start_matches('/'), "")
148151
.trim_start_matches('/'),
149152
);
150153

151-
let path = PathBuf::from(image_path);
152-
write_file(&path, &decode_base64(&image.content)?).await?;
154+
let path = PathBuf::from(document_path);
155+
write_file(&path, &decode_base64(&document.content)?).await?;
153156

154-
tracing::info!(?path, "Wrote embedded image",);
157+
tracing::info!(?path, "Wrote supporting document",);
155158
}
156159

157160
Ok(())

rfd-processor/src/rfd.rs

+7
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ impl PersistedRfd {
124124
})
125125
}
126126

127+
pub fn set_content(&mut self, format: ContentFormat, content: &str) {
128+
self.revision.content = content.to_string();
129+
self.revision.content_format = format;
130+
131+
*self.needs_update.lock().unwrap() = true;
132+
}
133+
127134
pub fn update_discussion(&mut self, new_discussion_url: impl ToString) -> Result<(), RfdError> {
128135
let new_discussion_url = new_discussion_url.to_string();
129136

rfd-processor/src/updater/copy_images_to_storage.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ impl RfdUpdateAction for CopyImagesToStorage {
2929

3030
let images = update
3131
.location
32-
.get_images(&ctx.github.client, &update.number)
32+
.download_supporting_documents(&ctx.github.client, &update.number)
3333
.await
3434
.map_err(|err| RfdUpdateActionErr::Continue(Box::new(err)))?;
3535

rfd-processor/src/updater/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use async_trait::async_trait;
66
use newtype_uuid::TypedUuid;
77
use octorust::types::{LabelsData, PullRequestData, PullRequestSimple};
8+
use process_includes::ProcessIncludes;
89
use rfd_data::content::RfdDocument;
910
use rfd_github::{GitHubError, GitHubRfdUpdate};
1011
use rfd_model::RfdId;
@@ -31,6 +32,7 @@ mod copy_images_to_storage;
3132
mod create_pull_request;
3233
mod ensure_default_state;
3334
mod ensure_pr_state;
35+
mod process_includes;
3436
mod update_discussion_url;
3537
mod update_pdfs;
3638
mod update_pull_request;
@@ -86,6 +88,7 @@ impl TryFrom<&str> for BoxedAction {
8688
"CreatePullRequest" => Ok(Box::new(CreatePullRequest)),
8789
"UpdatePullRequest" => Ok(Box::new(UpdatePullRequest)),
8890
"UpdateDiscussionUrl" => Ok(Box::new(UpdateDiscussionUrl)),
91+
"ProcessIncludes" => Ok(Box::new(ProcessIncludes)),
8992
"EnsureRfdWithPullRequestIsInValidState" => {
9093
Ok(Box::new(EnsureRfdWithPullRequestIsInValidState))
9194
}

0 commit comments

Comments
 (0)