|  | <!doctype html> | 
|  | <html> | 
|  | <head> | 
|  | <meta name="viewport" content="width=device-width"> | 
|  | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.css" /> | 
|  | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/styles/github-gist.min.css"> | 
|  | <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | 
|  | <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> | 
|  | <script src="https://unpkg.com/vue-async-computed@3.8.1"></script> | 
|  | <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/highlight.min.js"></script> | 
|  | <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></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; | 
|  | } | 
|  | .searchCondition > div { | 
|  | margin-right: 30px; | 
|  | } | 
|  | .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 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="version">version: </label> | 
|  | <select name="version" id="version" v-model="version"> | 
|  | <option v-for="option in versionOptions" v-bind:value="option"> | 
|  | {{ option }} | 
|  | </option> | 
|  | </select> | 
|  | </div> | 
|  | </div> | 
|  | <div v-html="aboutHtml"></div> | 
|  | <div v-html="configurationAboutHtml"></div> | 
|  | <div v-html="outputHtml"></div> | 
|  | </article> | 
|  | </div> | 
|  | <script> | 
|  | const RusfmtTagsUrl = 'https://api.github.com/repos/rust-lang/rustfmt/tags'; | 
|  | const RustfmtLatestUrl = 'https://api.github.com/repos/rust-lang/rustfmt/releases/latest'; | 
|  | 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) => { | 
|  | if (version === 'master') return 'master'; | 
|  | if (version.startsWith('v')) return version; | 
|  | return `v${version}`; | 
|  | }; | 
|  | const versionNumber = null !== versionParam ? parseVersionParam(versionParam) : 'master'; | 
|  | new Vue({ | 
|  | el: '#app', | 
|  | data: { | 
|  | aboutHtml: '', | 
|  | configurationAboutHtml: '', | 
|  | configurationDescriptions: [], | 
|  | searchCondition: searchTerm, | 
|  | shouldStable: false, | 
|  | version: versionNumber, | 
|  | oldVersion: undefined, | 
|  | versionOptions: ['master'], | 
|  | scrolledOnce: false, | 
|  | }, | 
|  | asyncComputed: { | 
|  | async updateVersion() { | 
|  | let latest; | 
|  | try { | 
|  | latest = (await axios.get(RustfmtLatestUrl)).data; | 
|  | } catch(err) { | 
|  | console.log(err); | 
|  | return; | 
|  | } | 
|  | if (versionParam == null) { | 
|  | this.version = latest.name; | 
|  | } | 
|  | }, | 
|  | async outputHtml() { | 
|  | if (this.version !== this.oldVersion) { | 
|  | const ConfigurationMdUrl = | 
|  | `https://raw.githubusercontent.com/rust-lang/rustfmt/${this.version}/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.oldVersion = this.version; | 
|  | } | 
|  |  | 
|  | 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.version); | 
|  | 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, | 
|  | }); | 
|  | } | 
|  | }, | 
|  | created: async function() { | 
|  | let tags; | 
|  | try { | 
|  | tags = (await axios.get(RusfmtTagsUrl)).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)); | 
|  | this.versionOptions = this.versionOptions.concat(tagOptions); | 
|  | }, | 
|  | 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>Ecountered 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> |