| <!doctype html> | |
| <html> | |
| <head> | |
| <meta name="viewport" content="width=device-width"> | |
| <title>Rustfmt</title> | |
| <link rel="stylesheet" | |
| href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css" | |
| integrity="sha512-7e0rszTy0dKTIgYzCeBXpmycq0EOkwAvxZ0dv/LAtQfcXWCoaRhzs+v2Mn6of3jImxPazbYxiK0kpE/7wZ/UQA==" | |
| crossorigin="anonymous" | |
| referrerpolicy="no-referrer" /> | |
| <link rel="stylesheet" | |
| href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/styles/github-gist.min.css" | |
| integrity="sha512-od7JLoOTxM8w/HSKGzP9Kexc20K9p/M2zxSWsd7H1e4Ctf+8SQFtCWEZnW5u6ul5ehSECa5QmOk9ju2nQMmlVA==" | |
| crossorigin="anonymous" | |
| referrerpolicy="no-referrer" /> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.2/marked.min.js" | |
| integrity="sha512-xeUh+KxNyTufZOje++oQHstlMQ8/rpyzPuM+gjMFYK3z5ILJGE7l2NvYL+XfliKURMpBIKKp1XoPN/qswlSMFA==" | |
| crossorigin="anonymous" | |
| referrerpolicy="no-referrer"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js" | |
| integrity="sha512-PwQ5+jgXxxprNGc80ycHE3spgj6TuDieHe/yTkbEJ+U5aol7dTupi/4VbwHHzlQVW77Vb0GLOIsiYigHgC5vcg==" | |
| crossorigin="anonymous" | |
| referrerpolicy="no-referrer"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/highlight.min.js" | |
| integrity="sha512-av6ZR84Ldk6j29DMhf6v0cssEhow1VROFLoKbX7wrvzZB++/nV8m0jXbYeWcHPEzSNONImx6zwBskCUT9AQidA==" | |
| crossorigin="anonymous" | |
| referrerpolicy="no-referrer"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js" | |
| integrity="sha512-3BBFWr73Xrf8GRjO+0pl0cbVwESBvg3ovnuCXpoqOkC/mkt/hTkFtutUPrwRz8eLySYvy5v1daulkyUZYvH8jw==" | |
| crossorigin="anonymous" | |
| referrerpolicy="no-referrer"></script> | |
| <script src="https://unpkg.com/vue-async-computed@3.8.1"></script> | |
| <style> | |
| @media (max-width: 767px) { | |
| .markdown-body { | |
| padding: 15px; | |
| } | |
| #search { | |
| max-width: 85%; | |
| } | |
| } | |
| body { | |
| overflow: scroll; | |
| } | |
| .markdown-body { | |
| box-sizing: border-box; | |
| min-width: 200px; | |
| max-width: 980px; | |
| margin: 0 auto; | |
| padding: 45px; | |
| } | |
| #search { | |
| border: 1px solid #d1d5da; | |
| padding-left: 30px; | |
| overflow: hidden; | |
| } | |
| .searchCondition { | |
| display: flex; | |
| flex-wrap: wrap; | |
| position: sticky; | |
| top: 0; | |
| z-index: 1; | |
| padding: 12px 0; | |
| background: #fff; | |
| border-bottom: 1px solid #d1d5da; | |
| } | |
| .searchCondition > div { | |
| margin-right: 30px; | |
| } | |
| .version-note { | |
| flex-basis: 100%; | |
| margin-top: 8px; | |
| color: #57606a; | |
| font-size: 0.9em; | |
| } | |
| .header-link { | |
| position: relative; | |
| } | |
| .header-link:hover::before { | |
| position: absolute; | |
| left: -2em; | |
| padding-right: 0.5em; | |
| content: '\2002\00a7\2002'; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="app"> | |
| <article class="markdown-body"> | |
| <div class="searchCondition"> | |
| <div> | |
| <form v-on:submit.prevent style="display:flex;"> | |
| <label for="search" style="margin-right: 3px;" >search:</label> | |
| <div style="position: relative;"> | |
| <input id="search" placeholder="Search all options" v-model="searchCondition"> | |
| <svg style="position: absolute; left: 8px; top: 7px;" class="octicon octicon-search subnav-search-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"> | |
| <path fill-rule="evenodd" d="M15.7 13.3l-3.81-3.83A5.93 5.93 0 0 0 13 6c0-3.31-2.69-6-6-6S1 2.69 1 6s2.69 6 6 6c1.3 0 2.48-.41 3.47-1.11l3.83 3.81c.19.2.45.3.7.3.25 0 .52-.09.7-.3a.996.996 0 0 0 0-1.41v.01zM7 10.7c-2.59 0-4.7-2.11-4.7-4.7 0-2.59 2.11-4.7 4.7-4.7 2.59 0 4.7 2.11 4.7 4.7 0 2.59-2.11 4.7-4.7 4.7z"></path> | |
| </svg> | |
| </div> | |
| </form> | |
| </div> | |
| <div> | |
| <label for="stable">stable: </label> | |
| <input type="checkbox" id="stable" v-model="shouldStable"> | |
| </div> | |
| <div> | |
| <label for="viewVersion">version: </label> | |
| <select name="viewVersion" id="viewVersion" v-model="viewVersion"> | |
| <option v-for="option in versionOptions" v-bind:value="option"> | |
| {{ option }} | |
| </option> | |
| </select> | |
| </div> | |
| <div class="version-note"> | |
| Configuration options can change between rustfmt versions. Select the version that matches the rustfmt you use. | |
| </div> | |
| </div> | |
| <div v-html="aboutHtml"></div> | |
| <div v-html="configurationAboutHtml"></div> | |
| <div v-html="outputHtml"></div> | |
| </article> | |
| </div> | |
| <script> | |
| const RustfmtTagsUrl = 'https://api.github.com/repos/rust-lang/rustfmt/tags'; | |
| const UrlHash = window.location.hash.replace(/^#/, ''); | |
| const queryParams = new URLSearchParams(window.location.search); | |
| const searchParam = queryParams.get('search'); | |
| const searchTerm = null !== searchParam ? searchParam : ''; | |
| const versionParam = queryParams.get('version'); | |
| const parseVersionParam = (version) => { | |
| // master is kept for backwards compatibility with old branch name | |
| if (version === 'master' || version === 'main') return 'main'; | |
| if (version.startsWith('v')) return version; | |
| return `v${version}`; | |
| }; | |
| const versionNumber = null !== versionParam ? parseVersionParam(versionParam) : 'main'; | |
| new Vue({ | |
| el: '#app', | |
| data: { | |
| aboutHtml: '', | |
| configurationAboutHtml: '', | |
| configurationDescriptions: [], | |
| searchCondition: searchTerm, | |
| shouldStable: false, | |
| viewVersion: versionNumber, | |
| oldViewVersion: undefined, | |
| versionOptions: ['main'], | |
| scrolledOnce: false, | |
| }, | |
| asyncComputed: { | |
| async pullTags() { | |
| let tags; | |
| try { | |
| tags = (await axios.get(RustfmtTagsUrl)).data; | |
| } catch(e) { | |
| this.handleReqFailure(e); | |
| return; | |
| } | |
| const excludedTagVersions = new Set(['v0.7', 'v0.8.1']); | |
| const tagOptions = tags | |
| .map(tag => tag.name) | |
| .filter(tag => tag.startsWith('v') && !excludedTagVersions.has(tag)); | |
| const latestRelease = tagOptions.find(tag => !tag.includes('rc')); | |
| this.viewVersion = latestRelease; | |
| this.versionOptions = this.versionOptions.concat(tagOptions); | |
| }, | |
| async outputHtml() { | |
| if (this.viewVersion !== this.oldViewVersion) { | |
| const ConfigurationMdUrl = | |
| `https://raw.githubusercontent.com/rust-lang/rustfmt/${this.viewVersion}/Configurations.md`; | |
| let res; | |
| try { | |
| res = await axios.get(ConfigurationMdUrl).catch(e => { throw e }); | |
| } catch(e) { | |
| this.handleReqFailure(e); | |
| return; | |
| } | |
| const { | |
| about, | |
| configurationAbout, | |
| configurationDescriptions | |
| } = parseMarkdownAst(res.data); | |
| this.aboutHtml = marked.parser(about); | |
| this.configurationAboutHtml = marked.parser(configurationAbout); | |
| this.configurationDescriptions = configurationDescriptions; | |
| this.oldViewVersion = this.viewVersion; | |
| } | |
| const ast = this.configurationDescriptions | |
| .filter(({ head, text, stable }) => { | |
| if (text.includes(this.searchCondition) === false && | |
| head.includes(this.searchCondition) === false) { | |
| return false; | |
| } | |
| return (this.shouldStable) | |
| ? stable === true | |
| : true; | |
| }) | |
| .reduce((stack, { value }) => { | |
| return stack.concat(value); | |
| }, []); | |
| ast.links = {}; | |
| queryParams.set('version', this.viewVersion); | |
| queryParams.set('search', this.searchCondition); | |
| const curUrl = window.location.pathname + | |
| '?' + queryParams.toString() + window.location.hash; | |
| history.pushState(null, '', curUrl); | |
| const renderer = new marked.Renderer(); | |
| renderer.heading = function(text, level) { | |
| const id = htmlToId(text); | |
| return `<h${level}> | |
| <a id="${id}" href="#${id}" name="${id}" class="header-link">${text}</a> | |
| </h${level}>`; | |
| }; | |
| return marked.parser(ast, { | |
| highlight(code, lang) { | |
| return hljs.highlight(lang ? lang : 'rust', code).value; | |
| }, | |
| headerIds: true, | |
| headerPrefix: '', | |
| renderer, | |
| }); | |
| } | |
| }, | |
| updated() { | |
| if (UrlHash === '') return; | |
| this.$nextTick(() => { | |
| const target = document.querySelector(`#${UrlHash}`); | |
| if (target != null && !this.scrolledOnce) { | |
| target.scrollIntoView(true); | |
| this.scrolledOnce = true; | |
| } | |
| }); | |
| }, | |
| methods: { | |
| handleReqFailure(e) { | |
| if (e.response.status === 404) { | |
| this.aboutHtml = | |
| "<p>Failed to get configuration options for this version, please select the version from the dropdown above.</p>"; | |
| } else if ( | |
| e.response.status === 403 && | |
| e.response.headers["X-RateLimit-Remaining"] === 0 | |
| ) { | |
| const resetDate = new Date( | |
| e.response.headers['X-RateLimit-Reset'] * 1000 | |
| ).toLocaleString(); | |
| this.aboutHtml = | |
| `<p>You have hit the GitHub API rate limit; documentation cannot be updated.` + | |
| `<p>The rate limit will be reset at ${resetDate}.</p>`; | |
| } else { | |
| this.aboutHtml = | |
| `<p>Encountered an error when fetching documentation data:</p>` + | |
| `<pre><code>${e.response.data}</code></pre>` + | |
| `<p>We would appreciate <a href="https://github.com/rust-lang/rustfmt/issues/new?template=bug_report.md">a bug report</a>.` + | |
| `<p>Try refreshing the page.</p>`; | |
| } | |
| } | |
| } | |
| }); | |
| const extractDepthOnes = (ast) => { | |
| return ast.reduce((stack, next) => { | |
| if (next.depth === 1) { | |
| stack.push([]); | |
| } | |
| const lastIndex = stack.length - 1; | |
| stack[lastIndex].push(next); | |
| return stack; | |
| }, []); | |
| } | |
| const extractDepthTwos = (ast) => { | |
| return ast.map((elem) => { | |
| return elem.reduce((stack, next) => { | |
| if (next.depth === 2) { | |
| stack.push([]); | |
| } | |
| const lastIndex = stack.length - 1; | |
| stack[lastIndex].push(next); | |
| return stack; | |
| }, | |
| [[]]); | |
| }); | |
| } | |
| const createHeadAndValue = (ast) => { | |
| return ast.map((elem) => { | |
| return elem.map((val) => { | |
| return { | |
| head: val[0].text, | |
| value: val, | |
| stable: val.some((elem) => { | |
| return elem.type === "list" && | |
| !!elem.raw && | |
| elem.raw.includes("**Stable**: Yes"); | |
| }), | |
| text: val.reduce((result, next) => { | |
| return next.text != null | |
| ? `${result} ${next.text}` | |
| : result; | |
| }, '') | |
| } | |
| }); | |
| }) | |
| } | |
| const parseMarkdownAst = (rawMarkdown) => { | |
| const ast = marked.lexer(rawMarkdown); | |
| const depthOnes = extractDepthOnes(ast); | |
| const depthTwos = extractDepthTwos(depthOnes); | |
| const [ | |
| abouts, configurations | |
| ] = createHeadAndValue(depthTwos); | |
| const about = abouts[0].value; | |
| about.links = {}; | |
| const [ | |
| configurationAbout, ...configurationDescriptions | |
| ] = configurations; | |
| configurationAbout.value.links = {}; | |
| return { | |
| about, | |
| configurationAbout: configurationAbout.value, | |
| configurationDescriptions | |
| }; | |
| } | |
| function htmlToId(text) { | |
| const tmpl = document.createElement('template'); | |
| tmpl.innerHTML = text.trim(); | |
| return encodeURIComponent(CSS.escape(tmpl.content.textContent)); | |
| } | |
| </script> | |
| </body> | |
| </html> |