use std::collections::BTreeMap; use std::io; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::LazyLock; use memchr::memmem::Finder; use serde::Deserialize; use thiserror::Error; use url::Url; use uv_configuration::SourceStrategy; use uv_normalize::PackageName; use uv_pep440::VersionSpecifiers; use uv_pypi_types::VerbatimParsedUrl; use uv_redacted::DisplaySafeUrl; use uv_settings::{GlobalOptions, ResolverInstallerSchema}; use uv_warnings::warn_user; use uv_workspace::pyproject::{ExtraBuildDependency, Sources}; static FINDER: LazyLock = LazyLock::new(|| Finder::new(b"# /// script")); /// A PEP 723 item, either read from a script on disk or provided via `stdin`. #[derive(Debug)] pub enum Pep723Item { /// A PEP 723 script read from disk. Script(Pep723Script), /// A PEP 723 script provided via `stdin`. Stdin(Pep723Metadata), /// A PEP 723 script provided via a remote URL. Remote(Pep723Metadata, DisplaySafeUrl), } impl Pep723Item { /// Return the [`Pep723Metadata`] associated with the item. pub fn metadata(&self) -> &Pep723Metadata { match self { Self::Script(script) => &script.metadata, Self::Stdin(metadata) => metadata, Self::Remote(metadata, ..) => metadata, } } /// Consume the item and return the associated [`Pep723Metadata`]. pub fn into_metadata(self) -> Pep723Metadata { match self { Self::Script(script) => script.metadata, Self::Stdin(metadata) => metadata, Self::Remote(metadata, ..) => metadata, } } /// Return the path of the PEP 723 item, if any. pub fn path(&self) -> Option<&Path> { match self { Self::Script(script) => Some(&script.path), Self::Stdin(..) => None, Self::Remote(..) => None, } } /// Return the PEP 723 script, if any. pub fn as_script(&self) -> Option<&Pep723Script> { match self { Self::Script(script) => Some(script), _ => None, } } } /// A reference to a PEP 723 item. #[derive(Debug, Copy, Clone)] pub enum Pep723ItemRef<'item> { /// A PEP 723 script read from disk. Script(&'item Pep723Script), /// A PEP 723 script provided via `stdin`. Stdin(&'item Pep723Metadata), /// A PEP 723 script provided via a remote URL. Remote(&'item Pep723Metadata, &'item Url), } impl Pep723ItemRef<'_> { /// Return the [`Pep723Metadata`] associated with the item. pub fn metadata(&self) -> &Pep723Metadata { match self { Self::Script(script) => &script.metadata, Self::Stdin(metadata) => metadata, Self::Remote(metadata, ..) => metadata, } } /// Return the path of the PEP 723 item, if any. pub fn path(&self) -> Option<&Path> { match self { Self::Script(script) => Some(&script.path), Self::Stdin(..) => None, Self::Remote(..) => None, } } /// Determine the working directory for the script. pub fn directory(&self) -> Result { match self { Self::Script(script) => Ok(std::path::absolute(&script.path)? .parent() .expect("script path has no parent") .to_owned()), Self::Stdin(..) | Self::Remote(..) => std::env::current_dir(), } } /// Collect any `tool.uv.index` from the script. pub fn indexes(&self, source_strategy: SourceStrategy) -> &[uv_distribution_types::Index] { match source_strategy { SourceStrategy::Enabled => self .metadata() .tool .as_ref() .and_then(|tool| tool.uv.as_ref()) .and_then(|uv| uv.top_level.index.as_deref()) .unwrap_or(&[]), SourceStrategy::Disabled => &[], } } /// Collect any `tool.uv.sources` from the script. pub fn sources(&self, source_strategy: SourceStrategy) -> &BTreeMap { static EMPTY: BTreeMap = BTreeMap::new(); match source_strategy { SourceStrategy::Enabled => self .metadata() .tool .as_ref() .and_then(|tool| tool.uv.as_ref()) .and_then(|uv| uv.sources.as_ref()) .unwrap_or(&EMPTY), SourceStrategy::Disabled => &EMPTY, } } } impl<'item> From<&'item Pep723Item> for Pep723ItemRef<'item> { fn from(item: &'item Pep723Item) -> Self { match item { Pep723Item::Script(script) => Self::Script(script), Pep723Item::Stdin(metadata) => Self::Stdin(metadata), Pep723Item::Remote(metadata, url) => Self::Remote(metadata, url), } } } impl<'item> From<&'item Pep723Script> for Pep723ItemRef<'item> { fn from(script: &'item Pep723Script) -> Self { Self::Script(script) } } /// A PEP 723 script, including its [`Pep723Metadata`]. #[derive(Debug, Clone)] pub struct Pep723Script { /// The path to the Python script. pub path: PathBuf, /// The parsed [`Pep723Metadata`] table from the script. pub metadata: Pep723Metadata, /// The content of the script before the metadata table. pub prelude: String, /// The content of the script after the metadata table. pub postlude: String, } impl Pep723Script { /// Read the PEP 723 `script` metadata from a Python file, if it exists. /// /// Returns `None` if the file is missing a PEP 723 metadata block. /// /// See: pub async fn read(file: impl AsRef) -> Result, Pep723Error> { let contents = fs_err::tokio::read(&file).await?; // Extract the `script` tag. let ScriptTag { prelude, metadata, postlude, } = match ScriptTag::parse(&contents) { Ok(Some(tag)) => tag, Ok(None) => return Ok(None), Err(err) => return Err(err), }; // Parse the metadata. let metadata = Pep723Metadata::from_str(&metadata)?; Ok(Some(Self { path: std::path::absolute(file)?, metadata, prelude, postlude, })) } /// Reads a Python script and generates a default PEP 723 metadata table. /// /// See: pub async fn init( file: impl AsRef, requires_python: &VersionSpecifiers, ) -> Result { let contents = fs_err::tokio::read(&file).await?; let (prelude, metadata, postlude) = Self::init_metadata(&contents, requires_python)?; Ok(Self { path: std::path::absolute(file)?, metadata, prelude, postlude, }) } /// Generates a default PEP 723 metadata table from the provided script contents. /// /// See: pub fn init_metadata( contents: &[u8], requires_python: &VersionSpecifiers, ) -> Result<(String, Pep723Metadata, String), Pep723Error> { // Define the default metadata. let default_metadata = if requires_python.is_empty() { indoc::formatdoc! {r" dependencies = [] ", } } else { indoc::formatdoc! {r#" requires-python = "{requires_python}" dependencies = [] "#, requires_python = requires_python, } }; let metadata = Pep723Metadata::from_str(&default_metadata)?; // Extract the shebang and script content. let (shebang, postlude) = extract_shebang(contents)?; // Add a newline to the beginning if it starts with a valid metadata comment line. let postlude = if postlude.strip_prefix('#').is_some_and(|postlude| { postlude .chars() .next() .is_some_and(|c| matches!(c, ' ' | '\r' | '\n')) }) { format!("\n{postlude}") } else { postlude }; Ok(( if shebang.is_empty() { String::new() } else { format!("{shebang}\n") }, metadata, postlude, )) } /// Create a PEP 723 script at the given path. pub async fn create( file: impl AsRef, requires_python: &VersionSpecifiers, existing_contents: Option>, ) -> Result<(), Pep723Error> { let file = file.as_ref(); let script_name = file .file_name() .and_then(|name| name.to_str()) .ok_or_else(|| Pep723Error::InvalidFilename(file.to_string_lossy().to_string()))?; let default_metadata = indoc::formatdoc! {r#" requires-python = "{requires_python}" dependencies = [] "#, }; let metadata = serialize_metadata(&default_metadata); let script = if let Some(existing_contents) = existing_contents { let (mut shebang, contents) = extract_shebang(&existing_contents)?; if !shebang.is_empty() { shebang.push_str("\n#\n"); // If the shebang doesn't contain `uv`, it's probably something like // `#! /usr/bin/env python`, which isn't going to respect the inline metadata. // Issue a warning for users who might not know that. // TODO: There are a lot of mistakes we could consider detecting here, like // `uv run` without `--script` when the file doesn't end in `.py`. if !regex::Regex::new(r"\buv\b").unwrap().is_match(&shebang) { warn_user!( "If you execute {} directly, it might ignore its inline metadata.\nConsider replacing its shebang with: {}", file.to_string_lossy().cyan(), "#!/usr/bin/env -S uv run --script".cyan(), ); } } indoc::formatdoc! {r" {shebang}{metadata} {contents}" } } else { indoc::formatdoc! {r#" {metadata} def main() -> None: print("Hello from {name}!") if __name__ == "__main__": main() "#, metadata = metadata, name = script_name, } }; Ok(fs_err::tokio::write(file, script).await?) } /// Replace the existing metadata in the file with new metadata and write the updated content. pub fn write(&self, metadata: &str) -> Result<(), io::Error> { let content = format!( "{}{}{}", self.prelude, serialize_metadata(metadata), self.postlude ); fs_err::write(&self.path, content)?; Ok(()) } /// Return the [`Sources`] defined in the PEP 723 metadata. pub fn sources(&self) -> &BTreeMap { static EMPTY: BTreeMap = BTreeMap::new(); self.metadata .tool .as_ref() .and_then(|tool| tool.uv.as_ref()) .and_then(|uv| uv.sources.as_ref()) .unwrap_or(&EMPTY) } } /// PEP 723 metadata as parsed from a `script` comment block. /// /// See: #[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct Pep723Metadata { pub dependencies: Option>>, pub requires_python: Option, pub tool: Option, /// The raw unserialized document. #[serde(skip)] pub raw: String, } impl Pep723Metadata { /// Parse the PEP 723 metadata from `stdin`. pub fn parse(contents: &[u8]) -> Result, Pep723Error> { // Extract the `script` tag. let ScriptTag { metadata, .. } = match ScriptTag::parse(contents) { Ok(Some(tag)) => tag, Ok(None) => return Ok(None), Err(err) => return Err(err), }; // Parse the metadata. Ok(Some(Self::from_str(&metadata)?)) } /// Read the PEP 723 `script` metadata from a Python file, if it exists. /// /// Returns `None` if the file is missing a PEP 723 metadata block. /// /// See: pub async fn read(file: impl AsRef) -> Result, Pep723Error> { let contents = fs_err::tokio::read(&file).await?; // Extract the `script` tag. let ScriptTag { metadata, .. } = match ScriptTag::parse(&contents) { Ok(Some(tag)) => tag, Ok(None) => return Ok(None), Err(err) => return Err(err), }; // Parse the metadata. Ok(Some(Self::from_str(&metadata)?)) } } impl FromStr for Pep723Metadata { type Err = toml::de::Error; /// Parse `Pep723Metadata` from a raw TOML string. fn from_str(raw: &str) -> Result { let metadata = toml::from_str(raw)?; Ok(Self { raw: raw.to_string(), ..metadata }) } } #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] pub struct Tool { pub uv: Option, } #[derive(Debug, Deserialize, Clone)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct ToolUv { #[serde(flatten)] pub globals: GlobalOptions, #[serde(flatten)] pub top_level: ResolverInstallerSchema, pub override_dependencies: Option>>, pub exclude_dependencies: Option>, pub constraint_dependencies: Option>>, pub build_constraint_dependencies: Option>>, pub extra_build_dependencies: Option>>, pub sources: Option>, } #[derive(Debug, Error)] pub enum Pep723Error { #[error( "An opening tag (`# /// script`) was found without a closing tag (`# ///`). Ensure that every line between the opening and closing tags (including empty lines) starts with a leading `#`." )] UnclosedBlock, #[error("The PEP 723 metadata block is missing from the script.")] MissingTag, #[error(transparent)] Io(#[from] io::Error), #[error(transparent)] Utf8(#[from] std::str::Utf8Error), #[error(transparent)] Toml(#[from] toml::de::Error), #[error("Invalid filename `{0}` supplied")] InvalidFilename(String), } #[derive(Debug, Clone, Eq, PartialEq)] pub struct ScriptTag { /// The content of the script before the metadata block. prelude: String, /// The metadata block. metadata: String, /// The content of the script after the metadata block. postlude: String, } impl ScriptTag { /// Given the contents of a Python file, extract the `script` metadata block with leading /// comment hashes removed, any preceding shebang or content (prelude), and the remaining Python /// script. /// /// Given the following input string representing the contents of a Python script: /// /// ```python /// #!/usr/bin/env python3 /// # /// script /// # requires-python = '>=3.11' /// # dependencies = [ /// # 'requests<3', /// # 'rich', /// # ] /// # /// /// /// import requests /// /// print("Hello, World!") /// ``` /// /// This function would return: /// /// - Preamble: `#!/usr/bin/env python3\n` /// - Metadata: `requires-python = '>=3.11'\ndependencies = [\n 'requests<3',\n 'rich',\n]` /// - Postlude: `import requests\n\nprint("Hello, World!")\n` /// /// See: pub fn parse(contents: &[u8]) -> Result, Pep723Error> { // Identify the opening pragma. let Some(index) = FINDER.find(contents) else { return Ok(None); }; // The opening pragma must be the first line, or immediately preceded by a newline. if !(index == 0 || matches!(contents[index - 1], b'\r' | b'\n')) { return Ok(None); } // Extract the preceding content. let prelude = std::str::from_utf8(&contents[..index])?; // Decode as UTF-8. let contents = &contents[index..]; let contents = std::str::from_utf8(contents)?; let mut lines = contents.lines(); // Ensure that the first line is exactly `# /// script`. if lines.next().is_none_or(|line| line != "# /// script") { return Ok(None); } // > Every line between these two lines (# /// TYPE and # ///) MUST be a comment starting // > with #. If there are characters after the # then the first character MUST be a space. The // > embedded content is formed by taking away the first two characters of each line if the // > second character is a space, otherwise just the first character (which means the line // > consists of only a single #). let mut toml = vec![]; for line in lines { // Remove the leading `#`. let Some(line) = line.strip_prefix('#') else { break; }; // If the line is empty, continue. if line.is_empty() { toml.push(""); continue; } // Otherwise, the line _must_ start with ` `. let Some(line) = line.strip_prefix(' ') else { break; }; toml.push(line); } // Find the closing `# ///`. The precedence is such that we need to identify the _last_ such // line. // // For example, given: // ```python // # /// script // # // # /// // # // # /// // ``` // // The latter `///` is the closing pragma let Some(index) = toml.iter().rev().position(|line| *line == "///") else { return Err(Pep723Error::UnclosedBlock); }; let index = toml.len() - index; // Discard any lines after the closing `# ///`. // // For example, given: // ```python // # /// script // # // # /// // # // # // ``` // // We need to discard the last two lines. toml.truncate(index - 1); // Join the lines into a single string. let prelude = prelude.to_string(); let metadata = toml.join("\n") + "\n"; let postlude = contents .lines() .skip(index + 1) .collect::>() .join("\n") + "\n"; Ok(Some(Self { prelude, metadata, postlude, })) } } /// Extracts the shebang line from the given file contents and returns it along with the remaining /// content. fn extract_shebang(contents: &[u8]) -> Result<(String, String), Pep723Error> { let contents = std::str::from_utf8(contents)?; if contents.starts_with("#!") { // Find the first newline. let bytes = contents.as_bytes(); let index = bytes .iter() .position(|&b| b == b'\r' || b == b'\n') .unwrap_or(bytes.len()); // Support `\r`, `\n`, and `\r\n` line endings. let width = match bytes.get(index) { Some(b'\r') => { if bytes.get(index + 1) == Some(&b'\n') { 2 } else { 1 } } Some(b'\n') => 1, _ => 0, }; // Extract the shebang line. let shebang = contents[..index].to_string(); let script = contents[index + width..].to_string(); Ok((shebang, script)) } else { Ok((String::new(), contents.to_string())) } } /// Formats the provided metadata by prefixing each line with `#` and wrapping it with script markers. fn serialize_metadata(metadata: &str) -> String { let mut output = String::with_capacity(metadata.len() + 32); output.push_str("# /// script"); output.push('\n'); for line in metadata.lines() { output.push('#'); if !line.is_empty() { output.push(' '); output.push_str(line); } output.push('\n'); } output.push_str("# ///"); output.push('\n'); output } #[cfg(test)] mod tests { use crate::{Pep723Error, Pep723Script, ScriptTag, serialize_metadata}; use std::str::FromStr; #[test] fn missing_space() { let contents = indoc::indoc! {r" # /// script #requires-python = '>=3.11' # /// "}; assert!(matches!( ScriptTag::parse(contents.as_bytes()), Err(Pep723Error::UnclosedBlock) )); } #[test] fn no_closing_pragma() { let contents = indoc::indoc! {r" # /// script # requires-python = '>=3.11' # dependencies = [ # 'requests<3', # 'rich', # ] "}; assert!(matches!( ScriptTag::parse(contents.as_bytes()), Err(Pep723Error::UnclosedBlock) )); } #[test] fn leading_content() { let contents = indoc::indoc! {r" pass # /// script # requires-python = '>=3.11' # dependencies = [ # 'requests<3', # 'rich', # ] # /// # # "}; assert_eq!(ScriptTag::parse(contents.as_bytes()).unwrap(), None); } #[test] fn simple() { let contents = indoc::indoc! {r" # /// script # requires-python = '>=3.11' # dependencies = [ # 'requests<3', # 'rich', # ] # /// import requests from rich.pretty import pprint resp = requests.get('https://peps.python.org/api/peps.json') data = resp.json() "}; let expected_metadata = indoc::indoc! {r" requires-python = '>=3.11' dependencies = [ 'requests<3', 'rich', ] "}; let expected_data = indoc::indoc! {r" import requests from rich.pretty import pprint resp = requests.get('https://peps.python.org/api/peps.json') data = resp.json() "}; let actual = ScriptTag::parse(contents.as_bytes()).unwrap().unwrap(); assert_eq!(actual.prelude, String::new()); assert_eq!(actual.metadata, expected_metadata); assert_eq!(actual.postlude, expected_data); } #[test] fn simple_with_shebang() { let contents = indoc::indoc! {r" #!/usr/bin/env python3 # /// script # requires-python = '>=3.11' # dependencies = [ # 'requests<3', # 'rich', # ] # /// import requests from rich.pretty import pprint resp = requests.get('https://peps.python.org/api/peps.json') data = resp.json() "}; let expected_metadata = indoc::indoc! {r" requires-python = '>=3.11' dependencies = [ 'requests<3', 'rich', ] "}; let expected_data = indoc::indoc! {r" import requests from rich.pretty import pprint resp = requests.get('https://peps.python.org/api/peps.json') data = resp.json() "}; let actual = ScriptTag::parse(contents.as_bytes()).unwrap().unwrap(); assert_eq!(actual.prelude, "#!/usr/bin/env python3\n".to_string()); assert_eq!(actual.metadata, expected_metadata); assert_eq!(actual.postlude, expected_data); } #[test] fn embedded_comment() { let contents = indoc::indoc! {r" # /// script # embedded-csharp = ''' # /// # /// text # /// # /// # public class MyClass { } # ''' # /// "}; let expected = indoc::indoc! {r" embedded-csharp = ''' /// /// text /// /// public class MyClass { } ''' "}; let actual = ScriptTag::parse(contents.as_bytes()) .unwrap() .unwrap() .metadata; assert_eq!(actual, expected); } #[test] fn trailing_lines() { let contents = indoc::indoc! {r" # /// script # requires-python = '>=3.11' # dependencies = [ # 'requests<3', # 'rich', # ] # /// # # "}; let expected = indoc::indoc! {r" requires-python = '>=3.11' dependencies = [ 'requests<3', 'rich', ] "}; let actual = ScriptTag::parse(contents.as_bytes()) .unwrap() .unwrap() .metadata; assert_eq!(actual, expected); } #[test] fn serialize_metadata_formatting() { let metadata = indoc::indoc! {r" requires-python = '>=3.11' dependencies = [ 'requests<3', 'rich', ] "}; let expected_output = indoc::indoc! {r" # /// script # requires-python = '>=3.11' # dependencies = [ # 'requests<3', # 'rich', # ] # /// "}; let result = serialize_metadata(metadata); assert_eq!(result, expected_output); } #[test] fn serialize_metadata_empty() { let metadata = ""; let expected_output = "# /// script\n# ///\n"; let result = serialize_metadata(metadata); assert_eq!(result, expected_output); } #[test] fn script_init_empty() { let contents = "".as_bytes(); let (prelude, metadata, postlude) = Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default()) .unwrap(); assert_eq!(prelude, ""); assert_eq!( metadata.raw, indoc::indoc! {r" dependencies = [] "} ); assert_eq!(postlude, ""); } #[test] fn script_init_requires_python() { let contents = "".as_bytes(); let (prelude, metadata, postlude) = Pep723Script::init_metadata( contents, &uv_pep440::VersionSpecifiers::from_str(">=3.8").unwrap(), ) .unwrap(); assert_eq!(prelude, ""); assert_eq!( metadata.raw, indoc::indoc! {r#" requires-python = ">=3.8" dependencies = [] "#} ); assert_eq!(postlude, ""); } #[test] fn script_init_with_hashbang() { let contents = indoc::indoc! {r#" #!/usr/bin/env python3 print("Hello, world!") "#} .as_bytes(); let (prelude, metadata, postlude) = Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default()) .unwrap(); assert_eq!(prelude, "#!/usr/bin/env python3\n"); assert_eq!( metadata.raw, indoc::indoc! {r" dependencies = [] "} ); assert_eq!( postlude, indoc::indoc! {r#" print("Hello, world!") "#} ); } #[test] fn script_init_with_other_metadata() { let contents = indoc::indoc! {r#" # /// noscript # Hello, # # World! # /// print("Hello, world!") "#} .as_bytes(); let (prelude, metadata, postlude) = Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default()) .unwrap(); assert_eq!(prelude, ""); assert_eq!( metadata.raw, indoc::indoc! {r" dependencies = [] "} ); // Note the extra line at the beginning. assert_eq!( postlude, indoc::indoc! {r#" # /// noscript # Hello, # # World! # /// print("Hello, world!") "#} ); } #[test] fn script_init_with_hashbang_and_other_metadata() { let contents = indoc::indoc! {r#" #!/usr/bin/env python3 # /// noscript # Hello, # # World! # /// print("Hello, world!") "#} .as_bytes(); let (prelude, metadata, postlude) = Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default()) .unwrap(); assert_eq!(prelude, "#!/usr/bin/env python3\n"); assert_eq!( metadata.raw, indoc::indoc! {r" dependencies = [] "} ); // Note the extra line at the beginning. assert_eq!( postlude, indoc::indoc! {r#" # /// noscript # Hello, # # World! # /// print("Hello, world!") "#} ); } #[test] fn script_init_with_valid_metadata_line() { let contents = indoc::indoc! {r#" # Hello, # /// noscript # # World! # /// print("Hello, world!") "#} .as_bytes(); let (prelude, metadata, postlude) = Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default()) .unwrap(); assert_eq!(prelude, ""); assert_eq!( metadata.raw, indoc::indoc! {r" dependencies = [] "} ); // Note the extra line at the beginning. assert_eq!( postlude, indoc::indoc! {r#" # Hello, # /// noscript # # World! # /// print("Hello, world!") "#} ); } #[test] fn script_init_with_valid_empty_metadata_line() { let contents = indoc::indoc! {r#" # # /// noscript # Hello, # World! # /// print("Hello, world!") "#} .as_bytes(); let (prelude, metadata, postlude) = Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default()) .unwrap(); assert_eq!(prelude, ""); assert_eq!( metadata.raw, indoc::indoc! {r" dependencies = [] "} ); // Note the extra line at the beginning. assert_eq!( postlude, indoc::indoc! {r#" # # /// noscript # Hello, # World! # /// print("Hello, world!") "#} ); } #[test] fn script_init_with_non_metadata_comment() { let contents = indoc::indoc! {r#" #Hello, # /// noscript # # World! # /// print("Hello, world!") "#} .as_bytes(); let (prelude, metadata, postlude) = Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default()) .unwrap(); assert_eq!(prelude, ""); assert_eq!( metadata.raw, indoc::indoc! {r" dependencies = [] "} ); assert_eq!( postlude, indoc::indoc! {r#" #Hello, # /// noscript # # World! # /// print("Hello, world!") "#} ); } }