diff --git a/src/librustdoc/flock.rs b/src/librustdoc/flock.rs new file mode 100644 index 00000000000..f5f75575113 --- /dev/null +++ b/src/librustdoc/flock.rs @@ -0,0 +1,191 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Simple file-locking apis for each OS. +//! +//! This is not meant to be in the standard library, it does nothing with +//! green/native threading. This is just a bare-bones enough solution for +//! librustdoc, it is not production quality at all. + +#[allow(non_camel_case_types)]; + +pub use self::imp::Lock; + +#[cfg(unix)] +mod imp { + use std::libc; + + #[cfg(target_os = "linux")] + mod os { + use std::libc; + + pub struct flock { + l_type: libc::c_short, + l_whence: libc::c_short, + l_start: libc::off_t, + l_len: libc::off_t, + l_pid: libc::pid_t, + + // not actually here, but brings in line with freebsd + l_sysid: libc::c_int, + } + + pub static F_WRLCK: libc::c_short = 1; + pub static F_UNLCK: libc::c_short = 2; + pub static F_SETLK: libc::c_int = 6; + pub static F_SETLKW: libc::c_int = 7; + } + + #[cfg(target_os = "freebsd")] + mod os { + use std::libc; + + pub struct flock { + l_start: libc::off_t, + l_len: libc::off_t, + l_pid: libc::pid_t, + l_type: libc::c_short, + l_whence: libc::c_short, + l_sysid: libc::c_int, + } + + pub static F_UNLCK: libc::c_short = 2; + pub static F_WRLCK: libc::c_short = 3; + pub static F_SETLK: libc::c_int = 12; + pub static F_SETLKW: libc::c_int = 13; + } + + #[cfg(target_os = "macos")] + mod os { + use std::libc; + + pub struct flock { + l_start: libc::off_t, + l_len: libc::off_t, + l_pid: libc::pid_t, + l_type: libc::c_short, + l_whence: libc::c_short, + + // not actually here, but brings in line with freebsd + l_sysid: libc::c_int, + } + + pub static F_UNLCK: libc::c_short = 2; + pub static F_WRLCK: libc::c_short = 3; + pub static F_SETLK: libc::c_int = 8; + pub static F_SETLKW: libc::c_int = 9; + } + + pub struct Lock { + priv fd: libc::c_int, + } + + impl Lock { + pub fn new(p: &Path) -> Lock { + let fd = p.with_c_str(|s| unsafe { + libc::open(s, libc::O_RDWR | libc::O_CREAT, libc::S_IRWXU) + }); + assert!(fd > 0); + let flock = os::flock { + l_start: 0, + l_len: 0, + l_pid: 0, + l_whence: libc::SEEK_SET as libc::c_short, + l_type: os::F_WRLCK, + l_sysid: 0, + }; + let ret = unsafe { + libc::fcntl(fd, os::F_SETLKW, &flock as *os::flock) + }; + if ret == -1 { + unsafe { libc::close(fd); } + fail!("could not lock `{}`", p.display()) + } + Lock { fd: fd } + } + } + + impl Drop for Lock { + fn drop(&mut self) { + let flock = os::flock { + l_start: 0, + l_len: 0, + l_pid: 0, + l_whence: libc::SEEK_SET as libc::c_short, + l_type: os::F_UNLCK, + l_sysid: 0, + }; + unsafe { + libc::fcntl(self.fd, os::F_SETLK, &flock as *os::flock); + libc::close(self.fd); + } + } + } +} + +#[cfg(windows)] +mod imp { + use std::libc; + use std::mem; + use std::os::win32::as_utf16_p; + use std::ptr; + + static LOCKFILE_EXCLUSIVE_LOCK: libc::DWORD = 0x00000002; + + extern "system" { + fn LockFileEx(hFile: libc::HANDLE, + dwFlags: libc::DWORD, + dwReserved: libc::DWORD, + nNumberOfBytesToLockLow: libc::DWORD, + nNumberOfBytesToLockHigh: libc::DWORD, + lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL; + fn UnlockFileEx(hFile: libc::HANDLE, + dwReserved: libc::DWORD, + nNumberOfBytesToLockLow: libc::DWORD, + nNumberOfBytesToLockHigh: libc::DWORD, + lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL; + } + + pub struct Lock { + priv handle: libc::HANDLE, + } + + impl Lock { + pub fn new(p: &Path) -> Lock { + let handle = as_utf16_p(p.as_str().unwrap(), |p| unsafe { + libc::CreateFileW(p, libc::GENERIC_READ, 0, ptr::mut_null(), + libc::CREATE_ALWAYS, + libc::FILE_ATTRIBUTE_NORMAL, + ptr::mut_null()) + }); + assert!(handle as uint != libc::INVALID_HANDLE_VALUE as uint); + let mut overlapped: libc::OVERLAPPED = unsafe { mem::init() }; + let ret = unsafe { + LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, 100, 0, + &mut overlapped) + }; + if ret == 0 { + unsafe { libc::CloseHandle(handle); } + fail!("could not lock `{}`", p.display()) + } + Lock { handle: handle } + } + } + + impl Drop for Lock { + fn drop(&mut self) { + let mut overlapped: libc::OVERLAPPED = unsafe { mem::init() }; + unsafe { + UnlockFileEx(self.handle, 0, 100, 0, &mut overlapped); + libc::CloseHandle(self.handle); + } + } + } +} diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index c49df49dc11..317571ebc3f 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -37,7 +37,7 @@ pub fn render( - + {favicon, select, none{} other{}} @@ -74,13 +74,6 @@ pub fn render(
- - - - -

Keyboard shortcuts

@@ -111,6 +104,14 @@ pub fn render(

+ + + + + ", diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 6ad7b7d9da1..26723482595 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -36,7 +36,7 @@ use std::fmt; use std::local_data; use std::io; -use std::io::{fs, File, BufferedWriter}; +use std::io::{fs, File, BufferedWriter, MemWriter, BufferedReader}; use std::str; use std::vec; use std::vec_ng::Vec; @@ -283,48 +283,75 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> { }; } - // Add all the static files - let mut dst = cx.dst.join(krate.name.as_slice()); - try!(mkdir(&dst)); - try!(write(dst.join("jquery.js"), - include_str!("static/jquery-2.1.0.min.js"))); - try!(write(dst.join("main.js"), include_str!("static/main.js"))); - try!(write(dst.join("main.css"), include_str!("static/main.css"))); - try!(write(dst.join("normalize.css"), - include_str!("static/normalize.css"))); - // Publish the search index - { - dst.push("search-index.js"); - let mut w = BufferedWriter::new(File::create(&dst).unwrap()); - let w = &mut w as &mut Writer; - try!(write!(w, "var searchIndex = [")); + let index = { + let mut w = MemWriter::new(); + try!(write!(&mut w, "searchIndex['{}'] = [", krate.name)); for (i, item) in cache.search_index.iter().enumerate() { if i > 0 { - try!(write!(w, ",")); + try!(write!(&mut w, ",")); } - try!(write!(w, "\\{ty:\"{}\",name:\"{}\",path:\"{}\",desc:{}", - item.ty, item.name, item.path, - item.desc.to_json().to_str())); + try!(write!(&mut w, "\\{ty:\"{}\",name:\"{}\",path:\"{}\",desc:{}", + item.ty, item.name, item.path, + item.desc.to_json().to_str())); match item.parent { Some(id) => { - try!(write!(w, ",parent:'{}'", id)); + try!(write!(&mut w, ",parent:'{}'", id)); } None => {} } - try!(write!(w, "\\}")); + try!(write!(&mut w, "\\}")); } - try!(write!(w, "];")); - try!(write!(w, "var allPaths = \\{")); + try!(write!(&mut w, "];")); + try!(write!(&mut w, "allPaths['{}'] = \\{", krate.name)); for (i, (&id, &(ref fqp, short))) in cache.paths.iter().enumerate() { if i > 0 { - try!(write!(w, ",")); + try!(write!(&mut w, ",")); } - try!(write!(w, "'{}':\\{type:'{}',name:'{}'\\}", - id, short, *fqp.last().unwrap())); + try!(write!(&mut w, "'{}':\\{type:'{}',name:'{}'\\}", + id, short, *fqp.last().unwrap())); } - try!(write!(w, "\\};")); - try!(w.flush()); + try!(write!(&mut w, "\\};")); + + str::from_utf8_owned(w.unwrap()).unwrap() + }; + + // Write out the shared files. Note that these are shared among all rustdoc + // docs placed in the output directory, so this needs to be a synchronized + // operation with respect to all other rustdocs running around. + { + try!(mkdir(&cx.dst)); + let _lock = ::flock::Lock::new(&cx.dst.join(".lock")); + + // Add all the static files. These may already exist, but we just + // overwrite them anyway to make sure that they're fresh and up-to-date. + try!(write(cx.dst.join("jquery.js"), + include_str!("static/jquery-2.1.0.min.js"))); + try!(write(cx.dst.join("main.js"), include_str!("static/main.js"))); + try!(write(cx.dst.join("main.css"), include_str!("static/main.css"))); + try!(write(cx.dst.join("normalize.css"), + include_str!("static/normalize.css"))); + + // Update the search index + let dst = cx.dst.join("search-index.js"); + let mut all_indexes = Vec::new(); + all_indexes.push(index); + if dst.exists() { + for line in BufferedReader::new(File::open(&dst)).lines() { + let line = try!(line); + if !line.starts_with("searchIndex") { continue } + if line.starts_with(format!("searchIndex['{}']", krate.name)) { + continue + } + all_indexes.push(line); + } + } + let mut w = try!(File::create(&dst)); + try!(writeln!(&mut w, r"var searchIndex = \{\}; var allPaths = \{\};")); + for index in all_indexes.iter() { + try!(writeln!(&mut w, "{}", *index)); + } + try!(writeln!(&mut w, "initSearch(searchIndex);")); } // Render all source files (this may turn into a giant no-op) diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index 3056bca68d7..ffdf67e56cf 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -9,7 +9,7 @@ // except according to those terms. /*jslint browser: true, es5: true */ -/*globals $: true, searchIndex: true, rootPath: true, allPaths: true */ +/*globals $: true, rootPath: true, allPaths: true */ (function() { "use strict"; @@ -23,7 +23,8 @@ map(function(s) { var pair = s.split("="); params[decodeURIComponent(pair[0])] = - typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]); + typeof pair[1] === "undefined" ? + null : decodeURIComponent(pair[1]); }); return params; } @@ -111,8 +112,9 @@ document.location.href = url; }); - function initSearch(searchIndex) { - var currentResults, index, params = getQueryStringParams(); + function initSearch(rawSearchIndex) { + var currentResults, index, searchIndex; + var params = getQueryStringParams(); // Populate search bar with query string search term when provided, // but only if the input bar is empty. This avoid the obnoxious issue @@ -126,7 +128,8 @@ * Executes the query and builds an index of results * @param {[Object]} query [The user query] * @param {[type]} max [The maximum results returned] - * @param {[type]} searchWords [The list of search words to query against] + * @param {[type]} searchWords [The list of search words to query + * against] * @return {[type]} [A search index of results] */ function execQuery(query, max, searchWords) { @@ -148,7 +151,9 @@ // quoted values mean literal search bb = searchWords.length; - if ((val.charAt(0) === "\"" || val.charAt(0) === "'") && val.charAt(val.length - 1) === val.charAt(0)) { + if ((val.charAt(0) === "\"" || val.charAt(0) === "'") && + val.charAt(val.length - 1) === val.charAt(0)) + { val = val.substr(1, val.length - 2); for (aa = 0; aa < bb; aa += 1) { if (searchWords[aa] === val) { @@ -166,7 +171,10 @@ val = val.replace(/\_/g, ""); for (var i = 0; i < split.length; i++) { for (aa = 0; aa < bb; aa += 1) { - if (searchWords[aa].indexOf(split[i]) > -1 || searchWords[aa].indexOf(val) > -1 || searchWords[aa].replace(/_/g, "").indexOf(val) > -1) { + if (searchWords[aa].indexOf(split[i]) > -1 || + searchWords[aa].indexOf(val) > -1 || + searchWords[aa].replace(/_/g, "").indexOf(val) > -1) + { // filter type: ... queries if (!typeFilter || typeFilter === searchIndex[aa].ty) { results.push([aa, searchWords[aa].replace(/_/g, "").indexOf(val)]); @@ -185,6 +193,7 @@ results[aa].push(searchIndex[results[aa][0]].path); results[aa].push(searchIndex[results[aa][0]].name); results[aa].push(searchIndex[results[aa][0]].parent); + results[aa].push(searchIndex[results[aa][0]].crate); } // if there are no results then return to default and fail if (results.length === 0) { @@ -193,7 +202,8 @@ // sort by exact match results.sort(function search_complete_sort0(aaa, bbb) { - if (searchWords[aaa[0]] === valLower && searchWords[bbb[0]] !== valLower) { + if (searchWords[aaa[0]] === valLower && + searchWords[bbb[0]] !== valLower) { return 1; } }); @@ -207,7 +217,8 @@ // second sorting attempt // sort by item name results.sort(function search_complete_sort1(aaa, bbb) { - if (searchWords[aaa[0]].length === searchWords[bbb[0]].length && searchWords[aaa[0]] > searchWords[bbb[0]]) { + if (searchWords[aaa[0]].length === searchWords[bbb[0]].length && + searchWords[aaa[0]] > searchWords[bbb[0]]) { return 1; } }); @@ -223,21 +234,26 @@ // fourth sorting attempt // sort by type results.sort(function search_complete_sort3(aaa, bbb) { - if (searchWords[aaa[0]] === searchWords[bbb[0]] && aaa[2] > bbb[2]) { + if (searchWords[aaa[0]] === searchWords[bbb[0]] && + aaa[2] > bbb[2]) { return 1; } }); // fifth sorting attempt // sort by path results.sort(function search_complete_sort4(aaa, bbb) { - if (searchWords[aaa[0]] === searchWords[bbb[0]] && aaa[2] === bbb[2] && aaa[3] > bbb[3]) { + if (searchWords[aaa[0]] === searchWords[bbb[0]] && + aaa[2] === bbb[2] && aaa[3] > bbb[3]) { return 1; } }); // sixth sorting attempt // remove duplicates, according to the data provided for (aa = results.length - 1; aa > 0; aa -= 1) { - if (searchWords[results[aa][0]] === searchWords[results[aa - 1][0]] && results[aa][2] === results[aa - 1][2] && results[aa][3] === results[aa - 1][3]) { + if (searchWords[results[aa][0]] === searchWords[results[aa - 1][0]] && + results[aa][2] === results[aa - 1][2] && + results[aa][3] === results[aa - 1][3]) + { results[aa][0] = -1; } } @@ -245,7 +261,7 @@ var result = results[i], name = result[4].toLowerCase(), path = result[3].toLowerCase(), - parent = allPaths[result[5]]; + parent = allPaths[result[6]][result[5]]; var valid = validateResult(name, path, split, parent); if (!valid) { @@ -256,11 +272,14 @@ } /** - * Validate performs the following boolean logic. For example: "File::open" will give - * IF A PARENT EXISTS => ("file" && "open") exists in (name || path || parent) - * OR => ("file" && "open") exists in (name || path ) + * Validate performs the following boolean logic. For example: + * "File::open" will give IF A PARENT EXISTS => ("file" && "open") + * exists in (name || path || parent) OR => ("file" && "open") exists in + * (name || path ) + * + * This could be written functionally, but I wanted to minimise + * functions on stack. * - * This could be written functionally, but I wanted to minimise functions on stack. * @param {[string]} name [The name of the result] * @param {[string]} path [The path of the result] * @param {[string]} keys [The keys to be used (["file", "open"])] @@ -273,8 +292,13 @@ //if there is a parent, then validate against parent if (parent !== undefined) { for (var i = 0; i < keys.length; i++) { - // if previous keys are valid and current key is in the path, name or parent - if ((validate) && (name.toLowerCase().indexOf(keys[i]) > -1 || path.toLowerCase().indexOf(keys[i]) > -1 || parent.name.toLowerCase().indexOf(keys[i]) > -1)) { + // if previous keys are valid and current key is in the + // path, name or parent + if ((validate) && + (name.toLowerCase().indexOf(keys[i]) > -1 || + path.toLowerCase().indexOf(keys[i]) > -1 || + parent.name.toLowerCase().indexOf(keys[i]) > -1)) + { validate = true; } else { validate = false; @@ -282,8 +306,12 @@ } } else { for (var i = 0; i < keys.length; i++) { - // if previous keys are valid and current key is in the path, name - if ((validate) && (name.toLowerCase().indexOf(keys[i]) > -1 || path.toLowerCase().indexOf(keys[i]) > -1)) { + // if previous keys are valid and current key is in the + // path, name + if ((validate) && + (name.toLowerCase().indexOf(keys[i]) > -1 || + path.toLowerCase().indexOf(keys[i]) > -1)) + { validate = true; } else { validate = false; @@ -298,7 +326,10 @@ matches = query.match(/^(fn|mod|str(uct)?|enum|trait|t(ype)?d(ef)?)\s*:\s*/i); if (matches) { - type = matches[1].replace(/^td$/, 'typedef').replace(/^str$/, 'struct').replace(/^tdef$/, 'typedef').replace(/^typed$/, 'typedef'); + type = matches[1].replace(/^td$/, 'typedef') + .replace(/^str$/, 'struct') + .replace(/^tdef$/, 'typedef') + .replace(/^typed$/, 'typedef'); query = query.substring(matches[0].length); } @@ -314,7 +345,6 @@ $results.on('click', function() { var dst = $(this).find('a')[0]; - console.log(window.location.pathname, dst.pathname); if (window.location.pathname == dst.pathname) { $('#search').addClass('hidden'); $('#main').removeClass('hidden'); @@ -362,7 +392,8 @@ var output, shown, query = getQuery(); currentResults = query.id; - output = '

Results for ' + query.query + (query.type ? ' (type: ' + query.type + ')' : '') + '

'; + output = '

Results for ' + query.query + + (query.type ? ' (type: ' + query.type + ')' : '') + '

'; output += ''; if (results.length > 0) { @@ -394,7 +425,7 @@ '/index.html" class="' + type + '">' + name + ''; } else if (item.parent !== undefined) { - var myparent = allPaths[item.parent]; + var myparent = allPaths[item.crate][item.parent]; var anchor = '#' + type + '.' + name; output += item.path + '::' + myparent.name + ':: yy[op].toLowerCase())) { - // return 1; - // } - // }); - // }; - showResults(results); } - function buildIndex(searchIndex) { - var len = searchIndex.length, - i = 0, - searchWords = []; + function buildIndex(rawSearchIndex) { + searchIndex = []; + var searchWords = []; + for (var crate in rawSearchIndex) { + if (!rawSearchIndex.hasOwnProperty(crate)) { continue } + var len = rawSearchIndex[crate].length; + var i = 0; - // before any analysis is performed lets gather the search terms to - // search against apart from the rest of the data. This is a quick - // operation that is cached for the life of the page state so that - // all other search operations have access to this cached data for - // faster analysis operations - for (i = 0; i < len; i += 1) { - if (typeof searchIndex[i].name === "string") { - searchWords.push(searchIndex[i].name.toLowerCase()); - } else { - searchWords.push(""); + // before any analysis is performed lets gather the search terms to + // search against apart from the rest of the data. This is a quick + // operation that is cached for the life of the page state so that + // all other search operations have access to this cached data for + // faster analysis operations + for (i = 0; i < len; i += 1) { + rawSearchIndex[crate][i].crate = crate; + searchIndex.push(rawSearchIndex[crate][i]); + if (typeof rawSearchIndex[crate][i].name === "string") { + var word = rawSearchIndex[crate][i].name.toLowerCase(); + searchWords.push(word); + } else { + searchWords.push(""); + } } } - return searchWords; } @@ -567,17 +542,21 @@ clearTimeout(keyUpTimeout); keyUpTimeout = setTimeout(search, 100); }); - // Push and pop states are used to add search results to the browser history. + + // Push and pop states are used to add search results to the browser + // history. if (browserSupportsHistoryApi()) { $(window).on('popstate', function(e) { var params = getQueryStringParams(); - // When browsing back from search results the main page visibility must be reset. + // When browsing back from search results the main page + // visibility must be reset. if (!params.search) { $('#main.content').removeClass('hidden'); $('#search.content').addClass('hidden'); } - // When browsing forward to search results the previous search will be repeated, - // so the currentResults are cleared to ensure the search is successful. + // When browsing forward to search results the previous + // search will be repeated, so the currentResults are + // cleared to ensure the search is successful. currentResults = null; // Synchronize search bar with query string state and // perform the search, but don't empty the bar if there's @@ -585,19 +564,46 @@ if (params.search !== undefined) { $('.search-input').val(params.search); } - // Some browsers fire 'onpopstate' for every page load (Chrome), while others fire the - // event only when actually popping a state (Firefox), which is why search() is called - // both here and at the end of the startSearch() function. + // Some browsers fire 'onpopstate' for every page load + // (Chrome), while others fire the event only when actually + // popping a state (Firefox), which is why search() is + // called both here and at the end of the startSearch() + // function. search(); }); } search(); } - index = buildIndex(searchIndex); + index = buildIndex(rawSearchIndex); startSearch(); + + // Draw a convenient sidebar of known crates if we have a listing + if (rootPath == '../') { + console.log('here'); + var sidebar = $('.sidebar'); + var div = $('
').attr('class', 'block crate'); + div.append($('

').text('Crates')); + + var crates = []; + for (var crate in rawSearchIndex) { + if (!rawSearchIndex.hasOwnProperty(crate)) { continue } + crates.push(crate); + } + crates.sort(); + for (var i = 0; i < crates.length; i++) { + var klass = 'crate'; + if (crates[i] == window.currentCrate) { + klass += ' current'; + } + div.append($('', {'href': '../' + crates[i] + '/index.html', + 'class': klass}).text(crates[i])); + div.append($('
')); + } + sidebar.append(div); + } } - initSearch(searchIndex); - + window.initSearch = initSearch; }()); + diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index e64292285d0..6dc3c2073f1 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -52,6 +52,7 @@ pub mod passes; pub mod plugins; pub mod visit_ast; pub mod test; +mod flock; pub static SCHEMA_VERSION: &'static str = "0.8.1"; diff --git a/src/libstd/libc.rs b/src/libstd/libc.rs index 0fb7a5f8503..42221f07449 100644 --- a/src/libstd/libc.rs +++ b/src/libstd/libc.rs @@ -3643,7 +3643,7 @@ pub mod funcs { pub fn open(path: *c_char, oflag: c_int, mode: c_int) -> c_int; pub fn creat(path: *c_char, mode: mode_t) -> c_int; - pub fn fcntl(fd: c_int, cmd: c_int) -> c_int; + pub fn fcntl(fd: c_int, cmd: c_int, ...) -> c_int; } }