Improve several aspects of the Rustdoc scrape-examples UI.
* Examples take up less screen height. * Snippets from binary crates are prioritized. * toggle-all-docs does not expand "More examples" sections.
This commit is contained in:
parent
01fbc5ae78
commit
6ccd14a782
@ -69,8 +69,8 @@ pub(crate) struct Options {
|
|||||||
pub(crate) input: PathBuf,
|
pub(crate) input: PathBuf,
|
||||||
/// The name of the crate being documented.
|
/// The name of the crate being documented.
|
||||||
pub(crate) crate_name: Option<String>,
|
pub(crate) crate_name: Option<String>,
|
||||||
/// Whether or not this is a proc-macro crate
|
/// The types of the crate being documented.
|
||||||
pub(crate) proc_macro_crate: bool,
|
pub(crate) crate_types: Vec<CrateType>,
|
||||||
/// How to format errors and warnings.
|
/// How to format errors and warnings.
|
||||||
pub(crate) error_format: ErrorOutputType,
|
pub(crate) error_format: ErrorOutputType,
|
||||||
/// Width of output buffer to truncate errors appropriately.
|
/// Width of output buffer to truncate errors appropriately.
|
||||||
@ -176,7 +176,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|||||||
f.debug_struct("Options")
|
f.debug_struct("Options")
|
||||||
.field("input", &self.input)
|
.field("input", &self.input)
|
||||||
.field("crate_name", &self.crate_name)
|
.field("crate_name", &self.crate_name)
|
||||||
.field("proc_macro_crate", &self.proc_macro_crate)
|
.field("crate_types", &self.crate_types)
|
||||||
.field("error_format", &self.error_format)
|
.field("error_format", &self.error_format)
|
||||||
.field("libs", &self.libs)
|
.field("libs", &self.libs)
|
||||||
.field("externs", &FmtExterns(&self.externs))
|
.field("externs", &FmtExterns(&self.externs))
|
||||||
@ -667,7 +667,6 @@ fn println_condition(condition: Condition) {
|
|||||||
None => OutputFormat::default(),
|
None => OutputFormat::default(),
|
||||||
};
|
};
|
||||||
let crate_name = matches.opt_str("crate-name");
|
let crate_name = matches.opt_str("crate-name");
|
||||||
let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro);
|
|
||||||
let playground_url = matches.opt_str("playground-url");
|
let playground_url = matches.opt_str("playground-url");
|
||||||
let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from);
|
let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from);
|
||||||
let module_sorting = if matches.opt_present("sort-modules-by-appearance") {
|
let module_sorting = if matches.opt_present("sort-modules-by-appearance") {
|
||||||
@ -718,7 +717,7 @@ fn println_condition(condition: Condition) {
|
|||||||
rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
|
rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
|
||||||
let options = Options {
|
let options = Options {
|
||||||
input,
|
input,
|
||||||
proc_macro_crate,
|
crate_types,
|
||||||
error_format,
|
error_format,
|
||||||
diagnostic_width,
|
diagnostic_width,
|
||||||
libs,
|
libs,
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
use rustc_middle::hir::nested_filter;
|
use rustc_middle::hir::nested_filter;
|
||||||
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
|
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
|
||||||
use rustc_resolve as resolve;
|
use rustc_resolve as resolve;
|
||||||
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
use rustc_session::config::{self, ErrorOutputType};
|
||||||
use rustc_session::lint;
|
use rustc_session::lint;
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::symbol::sym;
|
||||||
@ -203,7 +203,7 @@ pub(crate) fn create_config(
|
|||||||
RustdocOptions {
|
RustdocOptions {
|
||||||
input,
|
input,
|
||||||
crate_name,
|
crate_name,
|
||||||
proc_macro_crate,
|
crate_types,
|
||||||
error_format,
|
error_format,
|
||||||
diagnostic_width,
|
diagnostic_width,
|
||||||
libs,
|
libs,
|
||||||
@ -247,8 +247,6 @@ pub(crate) fn create_config(
|
|||||||
Some((lint.name_lower(), lint::Allow))
|
Some((lint.name_lower(), lint::Allow))
|
||||||
});
|
});
|
||||||
|
|
||||||
let crate_types =
|
|
||||||
if proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
|
|
||||||
let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false);
|
let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false);
|
||||||
// plays with error output here!
|
// plays with error output here!
|
||||||
let sessopts = config::Options {
|
let sessopts = config::Options {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
use rustc_parse::maybe_new_parser_from_source_str;
|
use rustc_parse::maybe_new_parser_from_source_str;
|
||||||
use rustc_parse::parser::attr::InnerAttrPolicy;
|
use rustc_parse::parser::attr::InnerAttrPolicy;
|
||||||
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
use rustc_session::config::{self, ErrorOutputType};
|
||||||
use rustc_session::parse::ParseSess;
|
use rustc_session::parse::ParseSess;
|
||||||
use rustc_session::{lint, Session};
|
use rustc_session::{lint, Session};
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
@ -68,8 +68,7 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
|
|||||||
|
|
||||||
debug!(?lint_opts);
|
debug!(?lint_opts);
|
||||||
|
|
||||||
let crate_types =
|
let crate_types = options.crate_types.clone();
|
||||||
if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
|
|
||||||
|
|
||||||
let sessopts = config::Options {
|
let sessopts = config::Options {
|
||||||
maybe_sysroot: options.maybe_sysroot.clone(),
|
maybe_sysroot: options.maybe_sysroot.clone(),
|
||||||
|
@ -2957,14 +2957,22 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
|
|||||||
|
|
||||||
// The call locations are output in sequence, so that sequence needs to be determined.
|
// The call locations are output in sequence, so that sequence needs to be determined.
|
||||||
// Ideally the most "relevant" examples would be shown first, but there's no general algorithm
|
// Ideally the most "relevant" examples would be shown first, but there's no general algorithm
|
||||||
// for determining relevance. Instead, we prefer the smallest examples being likely the easiest to
|
// for determining relevance. We instead make a proxy for relevance with the following heuristics:
|
||||||
// understand at a glance.
|
// 1. Code written to be an example is better than code not written to be an example, e.g.
|
||||||
|
// a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo directory
|
||||||
|
// structure in Rustdoc, so we proxy this by prioriting code that comes from a --crate-type bin.
|
||||||
|
// 2. Smaller examples are better than large examples. So we prioritize snippets that have the
|
||||||
|
// smallest line span for their enclosing item.
|
||||||
|
// 3. Finally we sort by the displayed file name, which is arbitrary but prevents the ordering
|
||||||
|
// of examples from randomly changing between Rustdoc invocations.
|
||||||
let ordered_locations = {
|
let ordered_locations = {
|
||||||
let sort_criterion = |(_, call_data): &(_, &CallData)| {
|
fn sort_criterion<'a>(
|
||||||
|
(_, call_data): &(&PathBuf, &'a CallData),
|
||||||
|
) -> (bool, u32, &'a String) {
|
||||||
// Use the first location because that's what the user will see initially
|
// Use the first location because that's what the user will see initially
|
||||||
let (lo, hi) = call_data.locations[0].enclosing_item.byte_span;
|
let (lo, hi) = call_data.locations[0].enclosing_item.byte_span;
|
||||||
hi - lo
|
(!call_data.is_bin, hi - lo, &call_data.display_name)
|
||||||
};
|
}
|
||||||
|
|
||||||
let mut locs = call_locations.iter().collect::<Vec<_>>();
|
let mut locs = call_locations.iter().collect::<Vec<_>>();
|
||||||
locs.sort_by_key(sort_criterion);
|
locs.sort_by_key(sort_criterion);
|
||||||
|
@ -1901,6 +1901,10 @@ in storage.js
|
|||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scraped-example {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.scraped-example .code-wrapper {
|
.scraped-example .code-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1909,16 +1913,31 @@ in storage.js
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scraped-example-title {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
background: white;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 5px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
box-shadow: 0 0 4px white;
|
||||||
|
}
|
||||||
|
|
||||||
.scraped-example:not(.expanded) .code-wrapper {
|
.scraped-example:not(.expanded) .code-wrapper {
|
||||||
max-height: 240px;
|
max-height: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scraped-example:not(.expanded) .code-wrapper pre {
|
.scraped-example:not(.expanded) .code-wrapper pre {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
max-height: 240px;
|
max-height: 120px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper,
|
||||||
|
.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre {
|
||||||
|
max-height: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
.scraped-example .code-wrapper .next,
|
.scraped-example .code-wrapper .next,
|
||||||
.scraped-example .code-wrapper .prev,
|
.scraped-example .code-wrapper .prev,
|
||||||
.scraped-example .code-wrapper .expand {
|
.scraped-example .code-wrapper .expand {
|
||||||
|
@ -622,7 +622,7 @@ function loadCss(cssUrl) {
|
|||||||
const innerToggle = document.getElementById(toggleAllDocsId);
|
const innerToggle = document.getElementById(toggleAllDocsId);
|
||||||
removeClass(innerToggle, "will-expand");
|
removeClass(innerToggle, "will-expand");
|
||||||
onEachLazy(document.getElementsByClassName("rustdoc-toggle"), e => {
|
onEachLazy(document.getElementsByClassName("rustdoc-toggle"), e => {
|
||||||
if (!hasClass(e, "type-contents-toggle")) {
|
if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) {
|
||||||
e.open = true;
|
e.open = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4,17 +4,19 @@
|
|||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
// Number of lines shown when code viewer is not expanded
|
// Number of lines shown when code viewer is not expanded
|
||||||
const MAX_LINES = 10;
|
const DEFAULT_MAX_LINES = 5;
|
||||||
|
const HIDDEN_MAX_LINES = 10;
|
||||||
|
|
||||||
// Scroll code block to the given code location
|
// Scroll code block to the given code location
|
||||||
function scrollToLoc(elt, loc) {
|
function scrollToLoc(elt, loc, isHidden) {
|
||||||
const lines = elt.querySelector(".src-line-numbers");
|
const lines = elt.querySelector(".src-line-numbers");
|
||||||
let scrollOffset;
|
let scrollOffset;
|
||||||
|
|
||||||
// If the block is greater than the size of the viewer,
|
// If the block is greater than the size of the viewer,
|
||||||
// then scroll to the top of the block. Otherwise scroll
|
// then scroll to the top of the block. Otherwise scroll
|
||||||
// to the middle of the block.
|
// to the middle of the block.
|
||||||
if (loc[1] - loc[0] > MAX_LINES) {
|
let maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES;
|
||||||
|
if (loc[1] - loc[0] > maxLines) {
|
||||||
const line = Math.max(0, loc[0] - 1);
|
const line = Math.max(0, loc[0] - 1);
|
||||||
scrollOffset = lines.children[line].offsetTop;
|
scrollOffset = lines.children[line].offsetTop;
|
||||||
} else {
|
} else {
|
||||||
@ -29,7 +31,7 @@
|
|||||||
elt.querySelector(".rust").scrollTo(0, scrollOffset);
|
elt.querySelector(".rust").scrollTo(0, scrollOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateScrapedExample(example) {
|
function updateScrapedExample(example, isHidden) {
|
||||||
const locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent);
|
const locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent);
|
||||||
let locIndex = 0;
|
let locIndex = 0;
|
||||||
const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight"));
|
const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight"));
|
||||||
@ -40,7 +42,7 @@
|
|||||||
const onChangeLoc = changeIndex => {
|
const onChangeLoc = changeIndex => {
|
||||||
removeClass(highlights[locIndex], "focus");
|
removeClass(highlights[locIndex], "focus");
|
||||||
changeIndex();
|
changeIndex();
|
||||||
scrollToLoc(example, locs[locIndex][0]);
|
scrollToLoc(example, locs[locIndex][0], isHidden);
|
||||||
addClass(highlights[locIndex], "focus");
|
addClass(highlights[locIndex], "focus");
|
||||||
|
|
||||||
const url = locs[locIndex][1];
|
const url = locs[locIndex][1];
|
||||||
@ -70,7 +72,7 @@
|
|||||||
expandButton.addEventListener("click", () => {
|
expandButton.addEventListener("click", () => {
|
||||||
if (hasClass(example, "expanded")) {
|
if (hasClass(example, "expanded")) {
|
||||||
removeClass(example, "expanded");
|
removeClass(example, "expanded");
|
||||||
scrollToLoc(example, locs[0][0]);
|
scrollToLoc(example, locs[0][0], isHidden);
|
||||||
} else {
|
} else {
|
||||||
addClass(example, "expanded");
|
addClass(example, "expanded");
|
||||||
}
|
}
|
||||||
@ -78,11 +80,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start with the first example in view
|
// Start with the first example in view
|
||||||
scrollToLoc(example, locs[0][0]);
|
scrollToLoc(example, locs[0][0], isHidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example");
|
const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example");
|
||||||
onEachLazy(firstExamples, updateScrapedExample);
|
onEachLazy(firstExamples, el => updateScrapedExample(el, false));
|
||||||
onEachLazy(document.querySelectorAll(".more-examples-toggle"), toggle => {
|
onEachLazy(document.querySelectorAll(".more-examples-toggle"), toggle => {
|
||||||
// Allow users to click the left border of the <details> section to close it,
|
// Allow users to click the left border of the <details> section to close it,
|
||||||
// since the section can be large and finding the [+] button is annoying.
|
// since the section can be large and finding the [+] button is annoying.
|
||||||
@ -99,7 +101,7 @@
|
|||||||
// depends on offsetHeight, a property that requires an element to be visible to
|
// depends on offsetHeight, a property that requires an element to be visible to
|
||||||
// compute correctly.
|
// compute correctly.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onEachLazy(moreExamples, updateScrapedExample);
|
onEachLazy(moreExamples, el => updateScrapedExample(el, true));
|
||||||
});
|
});
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
});
|
});
|
||||||
|
@ -774,6 +774,7 @@ fn main_args(at_args: &[String]) -> MainResult {
|
|||||||
let output_format = options.output_format;
|
let output_format = options.output_format;
|
||||||
let externs = options.externs.clone();
|
let externs = options.externs.clone();
|
||||||
let scrape_examples_options = options.scrape_examples_options.clone();
|
let scrape_examples_options = options.scrape_examples_options.clone();
|
||||||
|
let crate_types = options.crate_types.clone();
|
||||||
|
|
||||||
let config = core::create_config(options);
|
let config = core::create_config(options);
|
||||||
|
|
||||||
@ -832,7 +833,14 @@ fn main_args(at_args: &[String]) -> MainResult {
|
|||||||
info!("finished with rustc");
|
info!("finished with rustc");
|
||||||
|
|
||||||
if let Some(options) = scrape_examples_options {
|
if let Some(options) = scrape_examples_options {
|
||||||
return scrape_examples::run(krate, render_opts, cache, tcx, options);
|
return scrape_examples::run(
|
||||||
|
krate,
|
||||||
|
render_opts,
|
||||||
|
cache,
|
||||||
|
tcx,
|
||||||
|
options,
|
||||||
|
crate_types,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.crate_version = crate_version;
|
cache.crate_version = crate_version;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
opaque::{FileEncoder, MemDecoder},
|
opaque::{FileEncoder, MemDecoder},
|
||||||
Decodable, Encodable,
|
Decodable, Encodable,
|
||||||
};
|
};
|
||||||
use rustc_session::getopts;
|
use rustc_session::{config::CrateType, getopts};
|
||||||
use rustc_span::{
|
use rustc_span::{
|
||||||
def_id::{CrateNum, DefPathHash, LOCAL_CRATE},
|
def_id::{CrateNum, DefPathHash, LOCAL_CRATE},
|
||||||
edition::Edition,
|
edition::Edition,
|
||||||
@ -110,6 +110,7 @@ pub(crate) struct CallData {
|
|||||||
pub(crate) url: String,
|
pub(crate) url: String,
|
||||||
pub(crate) display_name: String,
|
pub(crate) display_name: String,
|
||||||
pub(crate) edition: Edition,
|
pub(crate) edition: Edition,
|
||||||
|
pub(crate) is_bin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type FnCallLocations = FxHashMap<PathBuf, CallData>;
|
pub(crate) type FnCallLocations = FxHashMap<PathBuf, CallData>;
|
||||||
@ -122,6 +123,7 @@ struct FindCalls<'a, 'tcx> {
|
|||||||
cx: Context<'tcx>,
|
cx: Context<'tcx>,
|
||||||
target_crates: Vec<CrateNum>,
|
target_crates: Vec<CrateNum>,
|
||||||
calls: &'a mut AllCallLocations,
|
calls: &'a mut AllCallLocations,
|
||||||
|
crate_types: Vec<CrateType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx>
|
impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx>
|
||||||
@ -245,7 +247,9 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
|
|||||||
let mk_call_data = || {
|
let mk_call_data = || {
|
||||||
let display_name = file_path.display().to_string();
|
let display_name = file_path.display().to_string();
|
||||||
let edition = call_span.edition();
|
let edition = call_span.edition();
|
||||||
CallData { locations: Vec::new(), url, display_name, edition }
|
let is_bin = self.crate_types.contains(&CrateType::Executable);
|
||||||
|
|
||||||
|
CallData { locations: Vec::new(), url, display_name, edition, is_bin }
|
||||||
};
|
};
|
||||||
|
|
||||||
let fn_key = tcx.def_path_hash(*def_id);
|
let fn_key = tcx.def_path_hash(*def_id);
|
||||||
@ -274,6 +278,7 @@ pub(crate) fn run(
|
|||||||
cache: formats::cache::Cache,
|
cache: formats::cache::Cache,
|
||||||
tcx: TyCtxt<'_>,
|
tcx: TyCtxt<'_>,
|
||||||
options: ScrapeExamplesOptions,
|
options: ScrapeExamplesOptions,
|
||||||
|
crate_types: Vec<CrateType>,
|
||||||
) -> interface::Result<()> {
|
) -> interface::Result<()> {
|
||||||
let inner = move || -> Result<(), String> {
|
let inner = move || -> Result<(), String> {
|
||||||
// Generates source files for examples
|
// Generates source files for examples
|
||||||
@ -300,7 +305,8 @@ pub(crate) fn run(
|
|||||||
|
|
||||||
// Run call-finder on all items
|
// Run call-finder on all items
|
||||||
let mut calls = FxHashMap::default();
|
let mut calls = FxHashMap::default();
|
||||||
let mut finder = FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates };
|
let mut finder =
|
||||||
|
FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, crate_types };
|
||||||
tcx.hir().visit_all_item_likes_in_crate(&mut finder);
|
tcx.hir().visit_all_item_likes_in_crate(&mut finder);
|
||||||
|
|
||||||
// The visitor might have found a type error, which we need to
|
// The visitor might have found a type error, which we need to
|
||||||
|
@ -25,3 +25,12 @@ store-property: (fullOffsetHeight, ".scraped-example-list > .scraped-example pre
|
|||||||
assert-property: (".scraped-example-list > .scraped-example pre", {
|
assert-property: (".scraped-example-list > .scraped-example pre", {
|
||||||
"scrollHeight": |fullOffsetHeight|
|
"scrollHeight": |fullOffsetHeight|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
assert-attribute-false: (".more-examples-toggle", {"open": ""})
|
||||||
|
click: ".more-examples-toggle"
|
||||||
|
assert-attribute: (".more-examples-toggle", {"open": ""})
|
||||||
|
click: "#toggle-all-docs"
|
||||||
|
assert-attribute-false: (".more-examples-toggle", {"open": ""})
|
||||||
|
// After re-opening the docs, the additional examples should stay closed
|
||||||
|
click: "#toggle-all-docs"
|
||||||
|
assert-attribute-false: (".more-examples-toggle", {"open": ""})
|
||||||
|
25
src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs
Normal file
25
src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
fn main() {
|
||||||
|
for i in 0..9 {
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
}
|
||||||
|
scrape_examples::test();
|
||||||
|
for i in 0..9 {
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
println!("hello world!");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user