├── .dir-locals.el ├── .github └── workflows │ ├── create_example_output.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── RELEASES.md ├── book ├── .gitignore ├── book.toml ├── full.render.js ├── panzoom.min.js ├── skill-tree.css ├── skill-tree.js ├── src │ ├── SUMMARY.md │ └── skill_tree.md └── viz.js ├── ci └── build-book.sh ├── example-trees ├── avd.toml └── stories.md ├── mdbook-skill-tree ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── js │ ├── README.md │ ├── full.render.js │ ├── panzoom.min.js │ ├── skill-tree.css │ ├── skill-tree.html │ ├── skill-tree.js │ └── viz.js └── src │ ├── main.rs │ ├── preprocessor.rs │ └── preprocessor │ └── test.rs ├── src ├── graphviz.rs ├── lib.rs ├── main.rs ├── test.rs └── tree.rs ├── test-data ├── avd_snippet.gv ├── avd_snippet.toml └── invalid_requires.toml └── tree-data └── example.toml /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((rust-mode (rust-format-on-save . t))) 2 | -------------------------------------------------------------------------------- /.github/workflows/create_example_output.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Create Example Output 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | # Generate output and modify it slightly to be more suitable for GitHub pages. 19 | - run: bash ci/build-book.sh 20 | 21 | - uses: JamesIves/github-pages-deploy-action@releases/v3 22 | with: 23 | ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | BRANCH: gh-pages # The branch the action should deploy to. 25 | FOLDER: book/book # The folder the action should deploy. 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.platform }}-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - run: rustup default ${{ matrix.channel }} 15 | - run: cargo build --verbose 16 | - run: cargo test --verbose --all 17 | - run: bash ci/build-book.sh 18 | strategy: 19 | matrix: 20 | platform: [ubuntu, macos, windows] 21 | channel: [stable, beta, nightly] 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | *~ 4 | *.dot 5 | *.svg 6 | output 7 | 8 | ### IDE files 9 | /.idea/ 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skill-tree" 3 | version = "3.2.1" 4 | authors = ["Niko Matsakis "] 5 | edition = "2018" 6 | description = "generate graphviz files to show roadmaps" 7 | license = "MIT" 8 | repository = "https://github.com/nikomatsakis/skill-tree" 9 | homepage = "https://github.com/nikomatsakis/skill-tree" 10 | 11 | [workspace] 12 | members = ["mdbook-skill-tree"] 13 | 14 | [dependencies] 15 | anyhow = "1.0" 16 | clap = "2.33.0" 17 | fehler = "1.0.0-alpha.2" 18 | structopt = "0.3.11" 19 | serde = "1.0" 20 | serde_derive = "1.0" 21 | svg = "0.5.12" 22 | toml = "0.5.1" 23 | htmlescape = "0.3.1" 24 | 25 | [dev-dependencies] 26 | regex = "1.0" 27 | prettydiff = "0.4" 28 | 29 | [profile.release.build-override] 30 | opt-level = 0 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skill-tree 2 | 3 | A tool for rendering "skill trees", currently using graphviz. 4 | 5 | ## What is a skill tree? 6 | 7 | A "skill tree" is a useful way to try and map out the "roadmap" for a 8 | project. The term is borrowed from video games, but it was first 9 | applied to project planning in this rather wonderful [blog post about 10 | WebAssembly's post-MVP future][wasm] (at least, that was the first 11 | time I'd seen it used that way). 12 | [wasm]: https://hacks.mozilla.org/2018/10/webassemblys-post-mvp-future/ 13 | 14 | See an [example skill tree](https://nikomatsakis.github.io/skill-tree/) 15 | in this project's website. 16 | 17 | ## How to use 18 | 19 | ### mdbook integration 20 | 21 | The main way to use this project is to integrate it into an mdbook. 22 | Use these steps to install it: 23 | 24 | * `cargo install mdbook-skill-tree` 25 | * installs the `mdbook-skill-tree` executable 26 | * in your mdbook's directory, `mdbook-skill-tree install` 27 | * updates your `book.toml` to contain the relevant javascript files 28 | * in your mdbook, add a `skill-tree` code block, [as seen here](book/src/skill_tree.md). 29 | 30 | ## run manually 31 | 32 | You can run `skill-tree` directly in which case it generates a `dot` file. 33 | For example: 34 | 35 | ```bash 36 | cargo run -- tree-data/example.toml example.dot 37 | ``` 38 | 39 | will transform the [`tree-data/example.toml`](tree-data/example.toml) 40 | file you can find in this repository. 41 | 42 | ## Next steps 43 | 44 | I should, of course, create a skill-tree for this project-- but the 45 | goal is to make this something that can be readily dropped into a 46 | working group repository (like [wg-traits]) and used to track the 47 | overall progress and plans of a project. The workflow isn't *quite* 48 | drag and drop enough for that yet, but we're close! 49 | 50 | [wg-traits]: https://github.com/rust-lang/wg-traits 51 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # 1.3.2 2 | 3 | * Remove outdated dependencies 4 | 5 | # 1.3.0 6 | 7 | * Fix bug around underline 8 | * Add ability to have `href` on tree nodes 9 | 10 | # 1.2.0 and before 11 | 12 | I wasn't taking notes =) 13 | -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Niko Matsakis"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Skill-tree Book" 7 | [preprocessor.skill-tree] 8 | command = "mdbook-skill-tree" 9 | [output.html] 10 | additional-css =["skill-tree.css"] 11 | additional-js =["viz.js", "panzoom.min.js", "full.render.js", "skill-tree.js"] 12 | -------------------------------------------------------------------------------- /book/panzoom.min.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.panzoom=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i0){transform.x+=diff;adjusted=true}diff=boundingBox.right-clientRect.left;if(diff<0){transform.x+=diff;adjusted=true}diff=boundingBox.top-clientRect.bottom;if(diff>0){transform.y+=diff;adjusted=true}diff=boundingBox.bottom-clientRect.top;if(diff<0){transform.y+=diff;adjusted=true}return adjusted}function getBoundingBox(){if(!bounds)return;if(typeof bounds==="boolean"){var ownerRect=owner.getBoundingClientRect();var sceneWidth=ownerRect.width;var sceneHeight=ownerRect.height;return{left:sceneWidth*boundsPadding,top:sceneHeight*boundsPadding,right:sceneWidth*(1-boundsPadding),bottom:sceneHeight*(1-boundsPadding)}}return bounds}function getClientRect(){var bbox=panController.getBBox();var leftTop=client(bbox.left,bbox.top);return{left:leftTop.x,top:leftTop.y,right:bbox.width*transform.scale+leftTop.x,bottom:bbox.height*transform.scale+leftTop.y}}function client(x,y){return{x:x*transform.scale+transform.x,y:y*transform.scale+transform.y}}function makeDirty(){isDirty=true;frameAnimation=window.requestAnimationFrame(frame)}function zoomByRatio(clientX,clientY,ratio){if(isNaN(clientX)||isNaN(clientY)||isNaN(ratio)){throw new Error("zoom requires valid numbers")}var newScale=transform.scale*ratio;if(newScalemaxZoom){if(transform.scale===maxZoom)return;ratio=maxZoom/transform.scale}var size=transformToScreen(clientX,clientY);transform.x=size.x-ratio*(size.x-transform.x);transform.y=size.y-ratio*(size.y-transform.y);if(bounds&&boundsPadding===1&&minZoom===1){transform.scale*=ratio;keepTransformInsideBounds()}else{var transformAdjusted=keepTransformInsideBounds();if(!transformAdjusted)transform.scale*=ratio}triggerEvent("zoom");makeDirty()}function zoomAbs(clientX,clientY,zoomLevel){var ratio=zoomLevel/transform.scale;zoomByRatio(clientX,clientY,ratio)}function centerOn(ui){var parent=ui.ownerSVGElement;if(!parent)throw new Error("ui element is required to be within the scene");var clientRect=ui.getBoundingClientRect();var cx=clientRect.left+clientRect.width/2;var cy=clientRect.top+clientRect.height/2;var container=parent.getBoundingClientRect();var dx=container.width/2-cx;var dy=container.height/2-cy;internalMoveBy(dx,dy,true)}function smoothMoveTo(x,y){internalMoveBy(x-transform.x,y-transform.y,true)}function internalMoveBy(dx,dy,smooth){if(!smooth){return moveBy(dx,dy)}if(moveByAnimation)moveByAnimation.cancel();var from={x:0,y:0};var to={x:dx,y:dy};var lastX=0;var lastY=0;moveByAnimation=animate(from,to,{step:function(v){moveBy(v.x-lastX,v.y-lastY);lastX=v.x;lastY=v.y}})}function scroll(x,y){cancelZoomAnimation();moveTo(x,y)}function dispose(){releaseEvents()}function listenForEvents(){owner.addEventListener("mousedown",onMouseDown,{passive:false});owner.addEventListener("dblclick",onDoubleClick,{passive:false});owner.addEventListener("touchstart",onTouch,{passive:false});owner.addEventListener("keydown",onKeyDown,{passive:false});wheel.addWheelListener(owner,onMouseWheel,{passive:false});makeDirty()}function releaseEvents(){wheel.removeWheelListener(owner,onMouseWheel);owner.removeEventListener("mousedown",onMouseDown);owner.removeEventListener("keydown",onKeyDown);owner.removeEventListener("dblclick",onDoubleClick);owner.removeEventListener("touchstart",onTouch);if(frameAnimation){window.cancelAnimationFrame(frameAnimation);frameAnimation=0}smoothScroll.cancel();releaseDocumentMouse();releaseTouches();textSelection.release();triggerPanEnd()}function frame(){if(isDirty)applyTransform()}function applyTransform(){isDirty=false;panController.applyTransform(transform);triggerEvent("transform");frameAnimation=0}function onKeyDown(e){var x=0,y=0,z=0;if(e.keyCode===38){y=1}else if(e.keyCode===40){y=-1}else if(e.keyCode===37){x=1}else if(e.keyCode===39){x=-1}else if(e.keyCode===189||e.keyCode===109){z=1}else if(e.keyCode===187||e.keyCode===107){z=-1}if(filterKey(e,x,y,z)){return}if(x||y){e.preventDefault();e.stopPropagation();var clientRect=owner.getBoundingClientRect();var offset=Math.min(clientRect.width,clientRect.height);var moveSpeedRatio=.05;var dx=offset*moveSpeedRatio*x;var dy=offset*moveSpeedRatio*y;internalMoveBy(dx,dy)}if(z){var scaleMultiplier=getScaleMultiplier(z*100);var offset=transformOrigin?getTransformOriginOffset():midPoint();publicZoomTo(offset.x,offset.y,scaleMultiplier)}}function midPoint(){var ownerRect=owner.getBoundingClientRect();return{x:ownerRect.width/2,y:ownerRect.height/2}}function onTouch(e){beforeTouch(e);if(e.touches.length===1){return handleSingleFingerTouch(e,e.touches[0])}else if(e.touches.length===2){pinchZoomLength=getPinchZoomLength(e.touches[0],e.touches[1]);multiTouch=true;startTouchListenerIfNeeded()}}function beforeTouch(e){if(options.onTouch&&!options.onTouch(e)){return}e.stopPropagation();e.preventDefault()}function beforeDoubleClick(e){if(options.onDoubleClick&&!options.onDoubleClick(e)){return}e.preventDefault();e.stopPropagation()}function handleSingleFingerTouch(e){var touch=e.touches[0];var offset=getOffsetXY(touch);lastSingleFingerOffset=offset;var point=transformToScreen(offset.x,offset.y);mouseX=point.x;mouseY=point.y;smoothScroll.cancel();startTouchListenerIfNeeded()}function startTouchListenerIfNeeded(){if(touchInProgress){return}touchInProgress=true;document.addEventListener("touchmove",handleTouchMove);document.addEventListener("touchend",handleTouchEnd);document.addEventListener("touchcancel",handleTouchEnd)}function handleTouchMove(e){if(e.touches.length===1){e.stopPropagation();var touch=e.touches[0];var offset=getOffsetXY(touch);var point=transformToScreen(offset.x,offset.y);var dx=point.x-mouseX;var dy=point.y-mouseY;if(dx!==0&&dy!==0){triggerPanStart()}mouseX=point.x;mouseY=point.y;internalMoveBy(dx,dy)}else if(e.touches.length===2){multiTouch=true;var t1=e.touches[0];var t2=e.touches[1];var currentPinchLength=getPinchZoomLength(t1,t2);var scaleMultiplier=1+(currentPinchLength/pinchZoomLength-1)*pinchSpeed;var firstTouchPoint=getOffsetXY(t1);var secondTouchPoint=getOffsetXY(t2);mouseX=(firstTouchPoint.x+secondTouchPoint.x)/2;mouseY=(firstTouchPoint.y+secondTouchPoint.y)/2;if(transformOrigin){var offset=getTransformOriginOffset();mouseX=offset.x;mouseY=offset.y}publicZoomTo(mouseX,mouseY,scaleMultiplier);pinchZoomLength=currentPinchLength;e.stopPropagation();e.preventDefault()}}function handleTouchEnd(e){if(e.touches.length>0){var offset=getOffsetXY(e.touches[0]);var point=transformToScreen(offset.x,offset.y);mouseX=point.x;mouseY=point.y}else{var now=new Date;if(now-lastTouchEndTime0)delta*=100;var scaleMultiplier=getScaleMultiplier(delta);if(scaleMultiplier!==1){var offset=transformOrigin?getTransformOriginOffset():getOffsetXY(e);publicZoomTo(offset.x,offset.y,scaleMultiplier);e.preventDefault()}}function getOffsetXY(e){var offsetX,offsetY;var ownerRect=owner.getBoundingClientRect();offsetX=e.clientX-ownerRect.left;offsetY=e.clientY-ownerRect.top;return{x:offsetX,y:offsetY}}function smoothZoom(clientX,clientY,scaleMultiplier){var fromValue=transform.scale;var from={scale:fromValue};var to={scale:scaleMultiplier*fromValue};smoothScroll.cancel();cancelZoomAnimation();zoomToAnimation=animate(from,to,{step:function(v){zoomAbs(clientX,clientY,v.scale)},done:triggerZoomEnd})}function smoothZoomAbs(clientX,clientY,toScaleValue){var fromValue=transform.scale;var from={scale:fromValue};var to={scale:toScaleValue};smoothScroll.cancel();cancelZoomAnimation();zoomToAnimation=animate(from,to,{step:function(v){zoomAbs(clientX,clientY,v.scale)}})}function getTransformOriginOffset(){var ownerRect=owner.getBoundingClientRect();return{x:ownerRect.width*transformOrigin.x,y:ownerRect.height*transformOrigin.y}}function publicZoomTo(clientX,clientY,scaleMultiplier){smoothScroll.cancel();cancelZoomAnimation();return zoomByRatio(clientX,clientY,scaleMultiplier)}function cancelZoomAnimation(){if(zoomToAnimation){zoomToAnimation.cancel();zoomToAnimation=null}}function getScaleMultiplier(delta){var sign=Math.sign(delta);var deltaAdjustedSpeed=Math.min(.25,Math.abs(speed*delta/128));return 1-sign*deltaAdjustedSpeed}function triggerPanStart(){if(!panstartFired){triggerEvent("panstart");panstartFired=true;smoothScroll.start()}}function triggerPanEnd(){if(panstartFired){if(!multiTouch)smoothScroll.stop();triggerEvent("panend")}}function triggerZoomEnd(){triggerEvent("zoomend")}function triggerEvent(name){api.fire(name,api)}}function parseTransformOrigin(options){if(!options)return;if(typeof options==="object"){if(!isNumber(options.x)||!isNumber(options.y))failTransformOrigin(options);return options}failTransformOrigin()}function failTransformOrigin(options){console.error(options);throw new Error(["Cannot parse transform origin.","Some good examples:",' "center center" can be achieved with {x: 0.5, y: 0.5}',' "top center" can be achieved with {x: 0.5, y: 0}',' "bottom right" can be achieved with {x: 1, y: 1}'].join("\n"))}function noop(){}function validateBounds(bounds){var boundsType=typeof bounds;if(boundsType==="undefined"||boundsType==="boolean")return;var validBounds=isNumber(bounds.left)&&isNumber(bounds.top)&&isNumber(bounds.bottom)&&isNumber(bounds.right);if(!validBounds)throw new Error("Bounds object is not valid. It can be: "+"undefined, boolean (true|false) or an object {left, top, right, bottom}")}function isNumber(x){return Number.isFinite(x)}function isNaN(value){if(Number.isNaN){return Number.isNaN(value)}return value!==value}function rigidScroll(){return{start:noop,stop:noop,cancel:noop}}function autoRun(){if(typeof document==="undefined")return;var scripts=document.getElementsByTagName("script");if(!scripts)return;var panzoomScript;for(var i=0;iminVelocity){ax=amplitude*vx;targetX+=ax}if(vy<-minVelocity||vy>minVelocity){ay=amplitude*vy;targetY+=ay}raf=requestAnimationFrame(autoScroll)}function autoScroll(){var elapsed=Date.now()-timestamp;var moving=false;var dx=0;var dy=0;if(ax){dx=-ax*Math.exp(-elapsed/timeConstant);if(dx>.5||dx<-.5)moving=true;else dx=ax=0}if(ay){dy=-ay*Math.exp(-elapsed/timeConstant);if(dy>.5||dy<-.5)moving=true;else dy=ay=0}if(moving){scroll(targetX+dx,targetY+dy);raf=requestAnimationFrame(autoScroll)}}}function getCancelAnimationFrame(){if(typeof cancelAnimationFrame==="function")return cancelAnimationFrame;return clearTimeout}function getRequestAnimationFrame(){if(typeof requestAnimationFrame==="function")return requestAnimationFrame;return function(handler){return setTimeout(handler,16)}}},{}],5:[function(require,module,exports){module.exports=makeSvgController;module.exports.canAttach=isSVGElement;function makeSvgController(svgElement,options){if(!isSVGElement(svgElement)){throw new Error("svg element is required for svg.panzoom to work")}var owner=svgElement.ownerSVGElement;if(!owner){throw new Error("Do not apply panzoom to the root element. "+"Use its child instead (e.g. ). "+"As of March 2016 only FireFox supported transform on the root element")}if(!options.disableKeyboardInteraction){owner.setAttribute("tabindex",0)}var api={getBBox:getBBox,getScreenCTM:getScreenCTM,getOwner:getOwner,applyTransform:applyTransform,initTransform:initTransform};return api;function getOwner(){return owner}function getBBox(){var bbox=svgElement.getBBox();return{left:bbox.x,top:bbox.y,width:bbox.width,height:bbox.height}}function getScreenCTM(){var ctm=owner.getCTM();if(!ctm){return owner.getScreenCTM()}return ctm}function initTransform(transform){var screenCTM=svgElement.getCTM();if(screenCTM===null){screenCTM=document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGMatrix()}transform.x=screenCTM.e;transform.y=screenCTM.f;transform.scale=screenCTM.a;owner.removeAttributeNS(null,"viewBox")}function applyTransform(transform){svgElement.setAttribute("transform","matrix("+transform.scale+" 0 0 "+transform.scale+" "+transform.x+" "+transform.y+")")}}function isSVGElement(element){return element&&element.ownerSVGElement&&element.getCTM}},{}],6:[function(require,module,exports){module.exports=Transform;function Transform(){this.x=0;this.y=0;this.scale=1}},{}],7:[function(require,module,exports){var BezierEasing=require("bezier-easing");var animations={ease:BezierEasing(.25,.1,.25,1),easeIn:BezierEasing(.42,0,1,1),easeOut:BezierEasing(0,0,.58,1),easeInOut:BezierEasing(.42,0,.58,1),linear:BezierEasing(0,0,1,1)};module.exports=animate;module.exports.makeAggregateRaf=makeAggregateRaf;module.exports.sharedScheduler=makeAggregateRaf();function animate(source,target,options){var start=Object.create(null);var diff=Object.create(null);options=options||{};var easing=typeof options.easing==="function"?options.easing:animations[options.easing];if(!easing){if(options.easing){console.warn("Unknown easing function in amator: "+options.easing)}easing=animations.ease}var step=typeof options.step==="function"?options.step:noop;var done=typeof options.done==="function"?options.done:noop;var scheduler=getScheduler(options.scheduler);var keys=Object.keys(target);keys.forEach(function(key){start[key]=source[key];diff[key]=target[key]-source[key]});var durationInMs=typeof options.duration==="number"?options.duration:400;var durationInFrames=Math.max(1,durationInMs*.06);var previousAnimationId;var frame=0;previousAnimationId=scheduler.next(loop);return{cancel:cancel};function cancel(){scheduler.cancel(previousAnimationId);previousAnimationId=0}function loop(){var t=easing(frame/durationInFrames);frame+=1;setValues(t);if(frame<=durationInFrames){previousAnimationId=scheduler.next(loop);step(source)}else{previousAnimationId=0;setTimeout(function(){done(source)},0)}}function setValues(t){keys.forEach(function(key){source[key]=diff[key]*t+start[key]})}}function noop(){}function getScheduler(scheduler){if(!scheduler){var canRaf=typeof window!=="undefined"&&window.requestAnimationFrame;return canRaf?rafScheduler():timeoutScheduler()}if(typeof scheduler.next!=="function")throw new Error("Scheduler is supposed to have next(cb) function");if(typeof scheduler.cancel!=="function")throw new Error("Scheduler is supposed to have cancel(handle) function");return scheduler}function rafScheduler(){return{next:window.requestAnimationFrame.bind(window),cancel:window.cancelAnimationFrame.bind(window)}}function timeoutScheduler(){return{next:function(cb){return setTimeout(cb,1e3/60)},cancel:function(id){return clearTimeout(id)}}}function makeAggregateRaf(){var frontBuffer=new Set;var backBuffer=new Set;var frameToken=0;return{next:next,cancel:next,clearAll:clearAll};function clearAll(){frontBuffer.clear();backBuffer.clear();cancelAnimationFrame(frameToken);frameToken=0}function next(callback){backBuffer.add(callback);renderNextFrame()}function renderNextFrame(){if(!frameToken)frameToken=requestAnimationFrame(renderFrame)}function renderFrame(){frameToken=0;var t=backBuffer;backBuffer=frontBuffer;frontBuffer=t;frontBuffer.forEach(function(callback){callback()});frontBuffer.clear()}function cancel(callback){backBuffer.delete(callback)}}},{"bezier-easing":8}],8:[function(require,module,exports){var NEWTON_ITERATIONS=4;var NEWTON_MIN_SLOPE=.001;var SUBDIVISION_PRECISION=1e-7;var SUBDIVISION_MAX_ITERATIONS=10;var kSplineTableSize=11;var kSampleStepSize=1/(kSplineTableSize-1);var float32ArraySupported=typeof Float32Array==="function";function A(aA1,aA2){return 1-3*aA2+3*aA1}function B(aA1,aA2){return 3*aA2-6*aA1}function C(aA1){return 3*aA1}function calcBezier(aT,aA1,aA2){return((A(aA1,aA2)*aT+B(aA1,aA2))*aT+C(aA1))*aT}function getSlope(aT,aA1,aA2){return 3*A(aA1,aA2)*aT*aT+2*B(aA1,aA2)*aT+C(aA1)}function binarySubdivide(aX,aA,aB,mX1,mX2){var currentX,currentT,i=0;do{currentT=aA+(aB-aA)/2;currentX=calcBezier(currentT,mX1,mX2)-aX;if(currentX>0){aB=currentT}else{aA=currentT}}while(Math.abs(currentX)>SUBDIVISION_PRECISION&&++i=NEWTON_MIN_SLOPE){return newtonRaphsonIterate(aX,guessForT,mX1,mX2)}else if(initialSlope===0){return guessForT}else{return binarySubdivide(aX,intervalStart,intervalStart+kSampleStepSize,mX1,mX2)}}return function BezierEasing(x){if(x===0){return 0}if(x===1){return 1}return calcBezier(getTForX(x),mY1,mY2)}}},{}],9:[function(require,module,exports){module.exports=function eventify(subject){validateSubject(subject);var eventsStorage=createEventsStorage(subject);subject.on=eventsStorage.on;subject.off=eventsStorage.off;subject.fire=eventsStorage.fire;return subject};function createEventsStorage(subject){var registeredEvents=Object.create(null);return{on:function(eventName,callback,ctx){if(typeof callback!=="function"){throw new Error("callback is expected to be a function")}var handlers=registeredEvents[eventName];if(!handlers){handlers=registeredEvents[eventName]=[]}handlers.push({callback:callback,ctx:ctx});return subject},off:function(eventName,callback){var wantToRemoveAll=typeof eventName==="undefined";if(wantToRemoveAll){registeredEvents=Object.create(null);return subject}if(registeredEvents[eventName]){var deleteAllCallbacksForEvent=typeof callback!=="function";if(deleteAllCallbacksForEvent){delete registeredEvents[eventName]}else{var callbacks=registeredEvents[eventName];for(var i=0;i1){fireArguments=Array.prototype.splice.call(arguments,1)}for(var i=0;i response.text()) 6 | .then(text => { 7 | viz.renderSVGElement(text) 8 | .then(element => { document.body.appendChild(element); }) 9 | }); 10 | } 11 | 12 | function setSvgSizeAndViewBox(svgElem) { 13 | // Same as --content-max-width 14 | const defaultWidth = "750px"; 15 | const defaultHeight = "500px"; 16 | // Make the element have a fixed size, but use the width/height 17 | // to set the viewBox 18 | let width = svgElem.getAttribute("width").slice(0, -2); 19 | let height = svgElem.getAttribute("height").slice(0, -2); 20 | let viewBox = "0 0 " + width + " " + height; 21 | svgElem.setAttribute("width", defaultWidth); 22 | svgElem.setAttribute("height", defaultHeight); 23 | svgElem.setAttribute("viewBox", viewBox); 24 | svgElem.setAttribute("class", "skills-tree"); 25 | } 26 | 27 | function appendControls(parent, svgElem, pz) { 28 | const pzXform = pz.getTransform();; 29 | const initialZoom = {x: pzXform.x, y: pzXform.y, scale: pzXform.scale}; 30 | const controls = document.createElement("div"); 31 | controls.setAttribute("style", "padding-bottom: 4px"); 32 | 33 | const controlsText = document.createElement("i"); 34 | controlsText.setAttribute("style", "font-size: smaller"); 35 | controlsText.innerText = "Hold the alt/option key to zoom the skill tree; click and move to pan."; 36 | 37 | const resetButton = document.createElement("button"); 38 | resetButton.setAttribute("title", "Reset"); 39 | resetButton.setAttribute("aria-label", "Reset pan/zoom"); 40 | resetButton.innerHTML = '' 41 | resetButton.appendChild(document.createTextNode(" Reset Pan/Zoom")); 42 | resetButton.onclick = (e) => { 43 | pz.moveTo(initialZoom.x, initialZoom.y); 44 | pz.zoomAbs(initialZoom.x, initialZoom.y, initialZoom.scale); 45 | } 46 | controls.appendChild(controlsText); 47 | controls.appendChild(resetButton); 48 | controls.className = 'buttons'; 49 | parent.insertBefore(controls, svgElem); 50 | } 51 | 52 | function convertDivToSkillTree(divId, dotText) { 53 | new Viz().renderSVGElement(dotText.dot_text).then(svg_elem => { 54 | let parent = document.getElementById(divId); 55 | parent.appendChild(svg_elem); 56 | setSvgSizeAndViewBox(svg_elem); 57 | let element = svg_elem.children[0]; 58 | let pz = panzoom(element, { 59 | bounds: true, 60 | boundsPadding: 0.1, 61 | beforeWheel: function(e) { 62 | // allow wheel-zoom only if altKey is down. Otherwise - ignore 63 | var shouldIgnore = !e.altKey; 64 | return shouldIgnore; 65 | } 66 | }); 67 | appendControls(parent, svg_elem, pz) 68 | }) 69 | } 70 | 71 | for (let obj of SKILL_TREES) { 72 | convertDivToSkillTree(obj.id, obj.value); 73 | } 74 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Skill Tree](./skill_tree.md) 4 | -------------------------------------------------------------------------------- /book/src/skill_tree.md: -------------------------------------------------------------------------------- 1 | # Skill Tree 2 | 3 | The "skill tree" package is a tool that you can use to plot out your overall trajectory. It is meant to be really easily integrated into `mdbook` but also usable stand-alone. 4 | 5 | ## Parts of a skill tree 6 | 7 | * Groups 8 | * Items 9 | 10 | ## Example 11 | 12 | Here is an example of what a skill tree looks like. Check out [the source on github][src]. 13 | 14 | [src]: https://github.com/nikomatsakis/skill-tree/tree/master/example-trees/avd.toml 15 | 16 | ``` 17 | {{#include ../../example-trees/avd.toml}} 18 | ``` 19 | 20 | Here is how it looks in rendered form: 21 | 22 | ```skill-tree 23 | {{#include ../../example-trees/avd.toml}} 24 | ``` -------------------------------------------------------------------------------- /book/viz.js: -------------------------------------------------------------------------------- 1 | /* 2 | Viz.js 2.1.2 (Graphviz 2.40.1, Expat 2.2.5, Emscripten 1.37.36) 3 | Copyright (c) 2014-2018 Michael Daines 4 | Licensed under MIT license 5 | 6 | This distribution contains other software in object code form: 7 | 8 | Graphviz 9 | Licensed under Eclipse Public License - v 1.0 10 | http://www.graphviz.org 11 | 12 | Expat 13 | Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper 14 | Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers. 15 | Licensed under MIT license 16 | http://www.libexpat.org 17 | 18 | zlib 19 | Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler 20 | http://www.zlib.net/zlib_license.html 21 | */ 22 | (function (global, factory) { 23 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 24 | typeof define === 'function' && define.amd ? define(factory) : 25 | (global.Viz = factory()); 26 | }(this, (function () { 'use strict'; 27 | 28 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 29 | return typeof obj; 30 | } : function (obj) { 31 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 32 | }; 33 | 34 | var classCallCheck = function (instance, Constructor) { 35 | if (!(instance instanceof Constructor)) { 36 | throw new TypeError("Cannot call a class as a function"); 37 | } 38 | }; 39 | 40 | var createClass = function () { 41 | function defineProperties(target, props) { 42 | for (var i = 0; i < props.length; i++) { 43 | var descriptor = props[i]; 44 | descriptor.enumerable = descriptor.enumerable || false; 45 | descriptor.configurable = true; 46 | if ("value" in descriptor) descriptor.writable = true; 47 | Object.defineProperty(target, descriptor.key, descriptor); 48 | } 49 | } 50 | 51 | return function (Constructor, protoProps, staticProps) { 52 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 53 | if (staticProps) defineProperties(Constructor, staticProps); 54 | return Constructor; 55 | }; 56 | }(); 57 | 58 | var _extends = Object.assign || function (target) { 59 | for (var i = 1; i < arguments.length; i++) { 60 | var source = arguments[i]; 61 | 62 | for (var key in source) { 63 | if (Object.prototype.hasOwnProperty.call(source, key)) { 64 | target[key] = source[key]; 65 | } 66 | } 67 | } 68 | 69 | return target; 70 | }; 71 | 72 | var WorkerWrapper = function () { 73 | function WorkerWrapper(worker) { 74 | var _this = this; 75 | 76 | classCallCheck(this, WorkerWrapper); 77 | 78 | this.worker = worker; 79 | this.listeners = []; 80 | this.nextId = 0; 81 | 82 | this.worker.addEventListener('message', function (event) { 83 | var id = event.data.id; 84 | var error = event.data.error; 85 | var result = event.data.result; 86 | 87 | _this.listeners[id](error, result); 88 | delete _this.listeners[id]; 89 | }); 90 | } 91 | 92 | createClass(WorkerWrapper, [{ 93 | key: 'render', 94 | value: function render(src, options) { 95 | var _this2 = this; 96 | 97 | return new Promise(function (resolve, reject) { 98 | var id = _this2.nextId++; 99 | 100 | _this2.listeners[id] = function (error, result) { 101 | if (error) { 102 | reject(new Error(error.message, error.fileName, error.lineNumber)); 103 | return; 104 | } 105 | resolve(result); 106 | }; 107 | 108 | _this2.worker.postMessage({ id: id, src: src, options: options }); 109 | }); 110 | } 111 | }]); 112 | return WorkerWrapper; 113 | }(); 114 | 115 | var ModuleWrapper = function ModuleWrapper(module, render) { 116 | classCallCheck(this, ModuleWrapper); 117 | 118 | var instance = module(); 119 | this.render = function (src, options) { 120 | return new Promise(function (resolve, reject) { 121 | try { 122 | resolve(render(instance, src, options)); 123 | } catch (error) { 124 | reject(error); 125 | } 126 | }); 127 | }; 128 | }; 129 | 130 | // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding 131 | 132 | 133 | function b64EncodeUnicode(str) { 134 | return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { 135 | return String.fromCharCode('0x' + p1); 136 | })); 137 | } 138 | 139 | function defaultScale() { 140 | if ('devicePixelRatio' in window && window.devicePixelRatio > 1) { 141 | return window.devicePixelRatio; 142 | } else { 143 | return 1; 144 | } 145 | } 146 | 147 | function svgXmlToImageElement(svgXml) { 148 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 149 | _ref$scale = _ref.scale, 150 | scale = _ref$scale === undefined ? defaultScale() : _ref$scale, 151 | _ref$mimeType = _ref.mimeType, 152 | mimeType = _ref$mimeType === undefined ? "image/png" : _ref$mimeType, 153 | _ref$quality = _ref.quality, 154 | quality = _ref$quality === undefined ? 1 : _ref$quality; 155 | 156 | return new Promise(function (resolve, reject) { 157 | var svgImage = new Image(); 158 | 159 | svgImage.onload = function () { 160 | var canvas = document.createElement('canvas'); 161 | canvas.width = svgImage.width * scale; 162 | canvas.height = svgImage.height * scale; 163 | 164 | var context = canvas.getContext("2d"); 165 | context.drawImage(svgImage, 0, 0, canvas.width, canvas.height); 166 | 167 | canvas.toBlob(function (blob) { 168 | var image = new Image(); 169 | image.src = URL.createObjectURL(blob); 170 | image.width = svgImage.width; 171 | image.height = svgImage.height; 172 | 173 | resolve(image); 174 | }, mimeType, quality); 175 | }; 176 | 177 | svgImage.onerror = function (e) { 178 | var error; 179 | 180 | if ('error' in e) { 181 | error = e.error; 182 | } else { 183 | error = new Error('Error loading SVG'); 184 | } 185 | 186 | reject(error); 187 | }; 188 | 189 | svgImage.src = 'data:image/svg+xml;base64,' + b64EncodeUnicode(svgXml); 190 | }); 191 | } 192 | 193 | function svgXmlToImageElementFabric(svgXml) { 194 | var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 195 | _ref2$scale = _ref2.scale, 196 | scale = _ref2$scale === undefined ? defaultScale() : _ref2$scale, 197 | _ref2$mimeType = _ref2.mimeType, 198 | mimeType = _ref2$mimeType === undefined ? 'image/png' : _ref2$mimeType, 199 | _ref2$quality = _ref2.quality, 200 | quality = _ref2$quality === undefined ? 1 : _ref2$quality; 201 | 202 | var multiplier = scale; 203 | 204 | var format = void 0; 205 | if (mimeType == 'image/jpeg') { 206 | format = 'jpeg'; 207 | } else if (mimeType == 'image/png') { 208 | format = 'png'; 209 | } 210 | 211 | return new Promise(function (resolve, reject) { 212 | fabric.loadSVGFromString(svgXml, function (objects, options) { 213 | // If there's something wrong with the SVG, Fabric may return an empty array of objects. Graphviz appears to give us at least one element back even given an empty graph, so we will assume an error in this case. 214 | if (objects.length == 0) { 215 | reject(new Error('Error loading SVG with Fabric')); 216 | } 217 | 218 | var element = document.createElement("canvas"); 219 | element.width = options.width; 220 | element.height = options.height; 221 | 222 | var canvas = new fabric.Canvas(element, { enableRetinaScaling: false }); 223 | var obj = fabric.util.groupSVGElements(objects, options); 224 | canvas.add(obj).renderAll(); 225 | 226 | var image = new Image(); 227 | image.src = canvas.toDataURL({ format: format, multiplier: multiplier, quality: quality }); 228 | image.width = options.width; 229 | image.height = options.height; 230 | 231 | resolve(image); 232 | }); 233 | }); 234 | } 235 | 236 | var Viz = function () { 237 | function Viz() { 238 | var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 239 | workerURL = _ref3.workerURL, 240 | worker = _ref3.worker, 241 | Module = _ref3.Module, 242 | render = _ref3.render; 243 | 244 | classCallCheck(this, Viz); 245 | 246 | if (typeof workerURL !== 'undefined') { 247 | this.wrapper = new WorkerWrapper(new Worker(workerURL)); 248 | } else if (typeof worker !== 'undefined') { 249 | this.wrapper = new WorkerWrapper(worker); 250 | } else if (typeof Module !== 'undefined' && typeof render !== 'undefined') { 251 | this.wrapper = new ModuleWrapper(Module, render); 252 | } else if (typeof Viz.Module !== 'undefined' && typeof Viz.render !== 'undefined') { 253 | this.wrapper = new ModuleWrapper(Viz.Module, Viz.render); 254 | } else { 255 | throw new Error('Must specify workerURL or worker option, Module and render options, or include one of full.render.js or lite.render.js after viz.js.'); 256 | } 257 | } 258 | 259 | createClass(Viz, [{ 260 | key: 'renderString', 261 | value: function renderString(src) { 262 | var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 263 | _ref4$format = _ref4.format, 264 | format = _ref4$format === undefined ? 'svg' : _ref4$format, 265 | _ref4$engine = _ref4.engine, 266 | engine = _ref4$engine === undefined ? 'dot' : _ref4$engine, 267 | _ref4$files = _ref4.files, 268 | files = _ref4$files === undefined ? [] : _ref4$files, 269 | _ref4$images = _ref4.images, 270 | images = _ref4$images === undefined ? [] : _ref4$images, 271 | _ref4$yInvert = _ref4.yInvert, 272 | yInvert = _ref4$yInvert === undefined ? false : _ref4$yInvert, 273 | _ref4$nop = _ref4.nop, 274 | nop = _ref4$nop === undefined ? 0 : _ref4$nop; 275 | 276 | for (var i = 0; i < images.length; i++) { 277 | files.push({ 278 | path: images[i].path, 279 | data: '\n\n' 280 | }); 281 | } 282 | 283 | return this.wrapper.render(src, { format: format, engine: engine, files: files, images: images, yInvert: yInvert, nop: nop }); 284 | } 285 | }, { 286 | key: 'renderSVGElement', 287 | value: function renderSVGElement(src) { 288 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 289 | 290 | return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) { 291 | var parser = new DOMParser(); 292 | return parser.parseFromString(str, 'image/svg+xml').documentElement; 293 | }); 294 | } 295 | }, { 296 | key: 'renderImageElement', 297 | value: function renderImageElement(src) { 298 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 299 | var scale = options.scale, 300 | mimeType = options.mimeType, 301 | quality = options.quality; 302 | 303 | 304 | return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) { 305 | if ((typeof fabric === 'undefined' ? 'undefined' : _typeof(fabric)) === "object" && fabric.loadSVGFromString) { 306 | return svgXmlToImageElementFabric(str, { scale: scale, mimeType: mimeType, quality: quality }); 307 | } else { 308 | return svgXmlToImageElement(str, { scale: scale, mimeType: mimeType, quality: quality }); 309 | } 310 | }); 311 | } 312 | }, { 313 | key: 'renderJSONObject', 314 | value: function renderJSONObject(src) { 315 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 316 | var format = options.format; 317 | 318 | 319 | if (format !== 'json' || format !== 'json0') { 320 | format = 'json'; 321 | } 322 | 323 | return this.renderString(src, _extends({}, options, { format: format })).then(function (str) { 324 | return JSON.parse(str); 325 | }); 326 | } 327 | }]); 328 | return Viz; 329 | }(); 330 | 331 | return Viz; 332 | 333 | }))); 334 | -------------------------------------------------------------------------------- /ci/build-book.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo install --vers ^0.3 mdbook --debug 3 | cargo install -f --path mdbook-skill-tree --debug 4 | cd book 5 | mdbook-skill-tree install 6 | mdbook build 7 | -------------------------------------------------------------------------------- /example-trees/avd.toml: -------------------------------------------------------------------------------- 1 | [graphviz] 2 | rankdir = "TD" 3 | 4 | [doc] 5 | columns = ["status", "assigned"] 6 | 7 | [doc.defaults] 8 | status = "tbd" 9 | assigned = "no" 10 | 11 | [doc.emoji.status] 12 | "tbd" = "▯▯▯▯▯" 13 | "exploration" = "▮▯▯▯▯" 14 | "design" = "▮▮▯▯▯" 15 | "implementation" = "▮▮▮▯▯" 16 | "stabilization" = "▮▮▮▮▯" 17 | "done" = "▮▮▮▮▮" 18 | 19 | [doc.emoji.assigned] 20 | "no" = "✋" 21 | "yes" = "✍️" 22 | "blocked" = "🛑" 23 | # "niko" = "" 24 | 25 | 26 | [[group]] 27 | name = "async-traits" 28 | label = "Async traits are possible" 29 | description = [ 30 | "Possible to write async abstractions using traits", 31 | "Working with dyn-safety requires careful work", 32 | ] 33 | items = [ 34 | { label = "Type alias impl Trait", status = "implementation" }, 35 | { label = "Generic associated types", status = "implementation" }, 36 | ] 37 | 38 | [[group]] 39 | name = "async-fn-everywhere" 40 | label = "Async fn everywhere" 41 | description = [ 42 | "Write async fn anywhere you can write fn", 43 | "Write async closures anywhere you can write sync closures", 44 | ] 45 | requires = [ 46 | "async-traits", 47 | ] 48 | items = [ 49 | { label = "Support for `dyn Trait` where `Trait` has async fn", status = "design" }, 50 | { label = "Async fn sugar in traits", status = "design", assigned = "niko" }, 51 | { label = "Async closure support", status = "design", assigned = "blocked" }, 52 | { label = "Boxable, recursive async fn", status = "design" }, 53 | ] 54 | 55 | [[group]] 56 | name = "async-iter" 57 | label = "Async iteration is awesome" 58 | description = [ 59 | "Use async iterators as easily as sync iterators", 60 | "Write async and sync iterators with equal ease", 61 | ] 62 | requires = [ 63 | "async-fn-everywhere", 64 | ] 65 | items = [ 66 | { label = "AsyncIterator trait", status = "implementation" }, 67 | { label = "Common combinators on AsyncIterator", status = "implementation", assigned = "blocked" }, 68 | { label = "Generators (both sync and async)", status = "design", assigned = "yes" }, 69 | { label = "Easy conversion of sync iter to async iter", status = "design", assigned = "blocked" }, 70 | ] 71 | 72 | [[group]] 73 | name = "async-read-and-write" 74 | label = "Async read and write are a pleasure to use" 75 | description = [ 76 | "Easy to pass around interoperable readers and writers", 77 | "Easy to impl AsyncRead and AsyncWrite traits", 78 | "Easy to write adapters that wrap async read and async write", 79 | ] 80 | requires = [ 81 | "async-fn-everywhere", 82 | ] 83 | items = [ 84 | { label = "AsyncRead trait in std", status = "implementation" }, 85 | { label = "AsyncWrite trait in std", status = "implementation" }, 86 | { label = "TBD: some way to write poll fns easily", status = "exploration" }, 87 | ] 88 | 89 | [[group]] 90 | name = "portability-is-possible" 91 | label = "Portability across runtimes is possible" 92 | description = [ 93 | "Grab a library from crates.io and
it works with your chosen runtime,
as long as the author does a good job", 94 | "Possible to author libraries
that can be used with many runtimes,
but requires careful use of traits", 95 | "Create a new runtime and have existing
(portable) libraries work with no modifications", 96 | ] 97 | requires = [ 98 | "async-iter", 99 | "async-read-and-write", 100 | ] 101 | items = [ 102 | { label = "Trait for spawning tasks", status = "exploration" }, 103 | { label = "Trait for spawning blocking tasks", status = "exploration" }, 104 | { label = "Trait for timers", status = "exploration" }, 105 | { label = "Common utilities like select, join, mutexes", status = "design" }, 106 | ] 107 | 108 | [[group]] 109 | name = "getting-started" 110 | label = "Getting started in async Rust is a smooth experience" 111 | description = [ 112 | "Easy to find, quality resources for learning async Rust", 113 | "Use Tcp Streams and other constructs without baking in a specific runtime or implementation", 114 | "Use Tcp Streams without threading generics all throughout your code", 115 | "Compiler and error messages help you avoid common mistakes", 116 | "Design patterns are well known", 117 | ] 118 | requires = [ 119 | "portability-is-possible", 120 | ] 121 | items = [ 122 | { label = "Improve async book structure", status = "implementation" }, 123 | { label = "Lint for blocking functions in an async context", status = "design" }, 124 | { label = "Lint holding things over an await that should not be held over an await", status = "implementation" }, 125 | { label = "Work on identifying common design patterns", status = "implementation" }, 126 | ] 127 | 128 | [[group]] 129 | name = "resolving-problems" 130 | label = "Resolving problems in running applications is easy" 131 | description = [ 132 | "Using a debugger with Rust basically works", 133 | "Find out what tasks your program currently has", 134 | "Show why tasks are blocked", 135 | "Detect common pitfalls and async hazards", 136 | ] 137 | requires = [ 138 | "getting-started", 139 | ] 140 | items = [ 141 | { label = "Better debuginfo", status = "exploration" }, 142 | { label = "Runtime Debugger interface", status = "exploration" }, 143 | { label = "Runtime bug detectors", status = "exploration" }, 144 | { label = "Improve async book structure", status = "implementation" }, 145 | { label = "Work on identifying common design patterns", status = "implementation" }, 146 | ] 147 | 148 | [[group]] 149 | name = "performance-tooling" 150 | label = "Me make Rust FAST" 151 | description = [ 152 | "Profiles of specific tasks (heap, memory, etc)", 153 | "Long-running sequential loops are easy to find and remedy", 154 | "Overall profiles", 155 | ] 156 | requires = [ 157 | "resolving-problems" 158 | ] 159 | items = [ 160 | { label = "Lint for functions that will take too long to execute", status = "design" }, 161 | { label = "Runtime warnings in debug mode", status = "exploration" }, 162 | { label = "Profiler-guided lints", status = "design" }, 163 | { label = "Combinators and hooks to make it easy to yield in long-running loops", status = "exploration" }, 164 | { label = "Highly optimized `spawn_blocking`", status = "exploration" }, 165 | { label = "Turbowish profiler support", status = "design" }, 166 | ] 167 | [[group]] 168 | name = "async-raii" 169 | label = "Easily manage async cleanup" 170 | description = [ 171 | "Add an async drop and be confident that it will be invoked without effort", 172 | "Reliably detect cases where sync drop might be used instead", 173 | ] 174 | requires = [ 175 | "async-traits", 176 | ] 177 | items = [ 178 | { label = "Ability to write an async disposal method", status = "design", assigned = "blocked" }, 179 | { label = "Lint for sync dropping when there's async drop", status = "design" }, 180 | ] 181 | 182 | # [[group]] 183 | # name = "first-class-learning-experience" 184 | # label = "First-class learning experience" 185 | # description = [ 186 | # "When async doesn't work as I expect
(whether at compilation time, runtime, debugging)...", 187 | # "something identifies the problem", 188 | # "something explains the problem", 189 | # "something proposes solutions", 190 | # "after reading the explanation and the solutions,
I understand what I did wrong", 191 | # ] 192 | # requires = [ 193 | # "resolving-problems", 194 | # "performance-tooling", 195 | # ] 196 | # items = [ 197 | # { label = "Cross-referencing between docs, lints, errors, and so forth", status = "exploration" }, 198 | # ] 199 | 200 | [[group]] 201 | name = "portability-across-send" 202 | label = "Portability across Send" 203 | description = [ 204 | "write code that can be Send or not-Send at zero-cost (e.g., use Rc vs Arc)", 205 | ] 206 | requires = [ 207 | "portability-is-possible", 208 | ] 209 | items = [ 210 | { label = "associated traits", status = "tbd" }, 211 | { label = "module-level generics", status = "tbd" }, 212 | ] 213 | 214 | [[group]] 215 | name = "ffcf" 216 | label = "If it compiles, it works" 217 | description = [ 218 | "Bugs are generally logic bugs, not a result of surprising async mechanisms", 219 | "Easy to create parallelism operating on borrowed data", 220 | "When tasks are no longer needed, they can be reliably canceled", 221 | "It is easy to visualize your program's task structure", 222 | ] 223 | requires = [ 224 | "async-raii", 225 | "getting-started", 226 | ] 227 | items = [ 228 | { label = "Way to avoid tasks being dropped unexpectedly while they continue to execute", status = "exploration" }, 229 | { label = "Mechanism for launching tasks within those scopes that can reference borrowed data", status = "design", assigned = "blocked" }, 230 | { label = "Hierarchical structure for tasks with cancelation propagation", status = "exploration" }, 231 | { label = "Lint when potentially canceling 'futures not known to be cancel safe'", status = "exploration" }, 232 | { label = "Integration into the visualization infrastructure and debug tools", status = "design", assigned = "blocked" }, 233 | ] 234 | 235 | [[group]] 236 | name = "zero-copy" 237 | label = "Zero copy works beautifully" 238 | description = [ 239 | "permit zero copy", 240 | ] 241 | requires = [ 242 | "ffcf", 243 | ] 244 | items = [ 245 | { label = "TBD" }, 246 | ] 247 | 248 | [[group]] 249 | name = "testing" 250 | label = "Testing your async code is easy" 251 | description = [ 252 | "Testing async code does not require a lot of pre-planning", 253 | "You can easily test connection errors, delays, and other corner cases", 254 | ] 255 | requires = [ 256 | "getting-started", 257 | ] 258 | items = [ 259 | { label = "Ability to fire timers programmatically (mock time)" }, 260 | { label = "`run_until_stalled`" }, 261 | { label = "Mock sockets and file objects?" }, 262 | { label = "Inject errors or long delays" }, 263 | { label = "Inject fake objects for real ones without modifying your code" }, 264 | ] 265 | 266 | 267 | # How to record out of scope things? 268 | 269 | -------------------------------------------------------------------------------- /example-trees/stories.md: -------------------------------------------------------------------------------- 1 | # Harmonic synthesis stories 2 | 3 | See also: 4 | 5 | * https://hackmd.io/YkY-62ZRQ9uevOD6iF7e0w?view 6 | 7 | # Characters 8 | 9 | Pick the least experienced character that you feel can be successful without help: 10 | 11 | * Niklaus: Minimal programming experience 12 | * Something that is 13 | * Alan: Significant general programming experience 14 | * Something you would only be able to do easily if you've been programming a while. 15 | * Grace: Significant systems programming experience 16 | * Something that you would only be able to do easily if you understand low-level system details. 17 | * Doesn't mean you've been coding in C++ 18 | * Building high performance systems in Java likely counts 19 | * Barbara: Significant Rust programming experience 20 | * Something that you would only be able to do easily if you understand Rust pretty well. 21 | 22 | # TODO 23 | 24 | * Send bounds for traits, where do they fit in? 25 | * Huge futures and how to manage them? 26 | 27 | # Template 28 | 29 | * Status quo stories 30 | * Story 31 | * Why X as the main character? 32 | * What are they happy with? 33 | * What are they disappointed with? 34 | 35 | # Async traits are possible 36 | 37 | * Status quo stories 38 | * Story 39 | * Barbara needs to make a trait for some async crates she is working on 40 | * Service trait `trait Service { type }` 41 | * Async fn trait (http requests) ... 42 | * Maybe Barbara helps Alan? 43 | * Barbara needs to write a function that requires the futures be send 44 | * It is kind of painful 45 | * Third: For dyn futures 46 | * Send, not Send 47 | * Why Barbara as the main protagonist? 48 | * To be successful, requires Rust fluency or at least a friend 49 | * What is she happy with? 50 | * It works and is possible 51 | * impl Trait and generic associated types enable a bunch of different things 52 | * What is she disappointed with? 53 | * She wishes she could write `async fn` 54 | * Addressed in async fn everywhere 55 | * Requiring that futures be send is pretty painful 56 | * Working with trait objects is a pain 57 | 58 | # Async fn everywhere 59 | 60 | * Status quo stories 61 | * Story 62 | * Part 1. Niklaus writes some async functions and closures. 63 | * Niklaus can write `async fn` in traits and things work 64 | * He is converting some code from a blog post 65 | * It uses a closure 66 | * He manages to map it to an async closure and it works 67 | * He writes a recursive functions and it's fine 68 | * To get dyn trait working? Has to consult Barbara, but there's a cryptic line of code he can write 69 | * Part 2. Grace explores dyn Trait 70 | * Grace explors the various knobs one can turn around dyn Trait 71 | * To get dyn trait working? Has to consult Barbara 72 | * To deal with Send bounds? What happens here? 73 | * Why Niklaus and Grace as the main protagonists? 74 | * Working with async fn and closures ought to be something one can learn without needing significant programming experience. 75 | * Why switch to Grace? To readily work through the options around dyn Trait requires understanding systems programming details. 76 | * What is he happy with? 77 | * Mostly he is able to just write "async fn" 78 | * What is he disappointed with? 79 | * Getting dyn trait working requires some semi-cryptic annotations with tradeoffs that he doesn't really understand 80 | * How can we extend the story to cover that part? 81 | 82 | # Async iteration is awesome 83 | 84 | * Status quo stories 85 | * Story 86 | * Why X as the main character? 87 | * What are they happy with? 88 | * What are they disappointed with? 89 | 90 | # Async read and write are a pleasure to use 91 | 92 | * Status quo stories 93 | * Story 94 | * Niklaus mixes and matches various crates from crates.io to do something cool 95 | * Niklaus has to manage input/output over the same stream 96 | * Alan wraps a reader to track bytes (??) 97 | * 98 | * Why Niklaus and Alan as the main character? 99 | * Niklaus for kind of basic programming tasks and how nice they are 100 | * Alan for bringing specific patterns to bear (wrapping readers) 101 | * What are they happy with? 102 | * Things work 103 | * What are they disappointed with? 104 | 105 | # Portability is possible 106 | 107 | * Status quo stories 108 | * Story 109 | * Alan grabs a DNS library and SLOW protocol 110 | * He has to write a bit of glue code to connect it up 111 | * Alan writes a library that works across runtimes 112 | * He has to use the right traits in the right places 113 | * Uses `dyn Trait` to reduce generics 114 | * Alan loads another library 115 | * They use generic parameters everywhere 116 | * Why Alan as the main character? 117 | * Q: Is this easy enough for Niklaus? 118 | * What are they happy with? 119 | * It is possible 120 | * What are they disappointed with? 121 | * Glue code is a bit annoying compared to node.js or what have you 122 | * Not everything they want is in the trait 123 | * Sometimes they encounter libraries that just bake in a runtime because you can't make the most ergonomic APIs you might want 124 | 125 | # Getting started in async Rust is a smooth experience 126 | 127 | * Status quo stories 128 | * Story 129 | * Niklaus wants to learn async Rust 130 | * Niklaus comes to the async book, it recommends that a list of runtims 131 | * "Specialized for different purposes" 132 | * "Pick one for now, it's easy to change later" 133 | * Niklaus writes `async fn main` with their chosen runtime 134 | * Niklaus gets lints that guide him against blocking functions and give helpful suggestons 135 | * Niklaus gets a warning about a really large future and a suggestion to add `repr(box)` 136 | * He is referred to the book and reads about it 137 | * He resolves to dig a bit more later on 138 | * Niklaus gets referred to the book by a lint which explains some problem 139 | * The book suggests a design pattern 140 | * He tries it and feels he learned something 141 | * Niklaus experiments with other runtimes; they seem to work. He is happy. 142 | * Why Niklaus as the main character? 143 | * Our goal is that people should be able to learn Async Rust with minimal programming experience and be productive. 144 | * What are they happy with? 145 | * What are they disappointed with? 146 | 147 | # Resolving problems in running applications is easy 148 | 149 | * Status quo stories 150 | * Story 151 | * Alan has a simple logic bug 152 | * He opens gdb and rr, explores 153 | * He is able to set breakpoints, use next and reverse, and that sort of thing 154 | * Later, Alan has a blocked task 155 | * He opens up gdb but `info threads` is not useful 156 | * He pops open the book and learns about Turbowish 157 | * He goes to Turbowish and gets a list of his tasks 158 | * He sees a :warning: emoji and goes to investigate 159 | * Ah-ha, seems like the problem is X 160 | * He sees other flags popping up and fixes some other problems 161 | * Why Alan as the main character? 162 | * Assuming experience with debuggers and resolving problems 163 | * What are they happy with? 164 | * Tools that work pretty well 165 | * What are they disappointed with? 166 | * A few too many distinct tools 167 | * Integration into gdb/rr might be even better 168 | 169 | # Me make Rust FAST 170 | 171 | * Status quo stories 172 | * Story 173 | * A client complains that their requests are slow 174 | * Pop open turbowish, filter down to tasks from that client 175 | * Compare to the general flamegraph 176 | * Ah, this peak looks different 177 | * Investigate, find an O(n^2) loop (or something) 178 | * Warning about large futures that are being copied 179 | * Advice based on production profiling data 180 | * Why Grace as the main character? 181 | * What are they happy with? 182 | * What are they disappointed with? 183 | 184 | # Easily manage async cleanup 185 | 186 | * Status quo stories 187 | * Story 188 | * Alan is working with an SQLite database 189 | * Drop works as expected 190 | * Ability to highlight a specified "close" function that is *not* Drop, perhaps it takes parameters 191 | * Some kind of warning for sequential drop when async drop is available 192 | * Perhaps a case where it goes awry -- and what happens, anyway? -- when using a generic function! 193 | * Why Alan as the main character? 194 | * Not sure! Maybe Niklaus 195 | * What are they happy with? 196 | * Things mostly work 197 | * What are they disappointed with? 198 | * They would prefer static enforcement 199 | 200 | # Portability across Send 201 | 202 | * Status quo stories 203 | * Story 204 | * 205 | * Why Alan as the main character? 206 | * What are they happy with? 207 | * What are they disappointed with? 208 | 209 | # If it compiles, it works 210 | 211 | * Status quo stories 212 | * Story 213 | * Alan is working on 214 | * Why Alan as the main character? 215 | * "If it compiles, it works" requires some level of experience to get the logic right. 216 | * What are they happy with? 217 | * What are they disappointed with? 218 | 219 | # Zero copy 220 | 221 | * Status quo stories 222 | * Story 223 | * 224 | * Why Alan as the main character? 225 | * What are they happy with? 226 | * What are they disappointed with? 227 | 228 | # Testing your async code is easy 229 | 230 | * Status quo stories 231 | * Story 232 | * 233 | * Why Alan as the main character? 234 | * What are they happy with? 235 | * What are they disappointed with? 236 | 237 | # Long-running sequential loops are easy to find and remedy 238 | 239 | * Status quo stories 240 | * Story 241 | * 242 | * Why Alan as the main character? 243 | * What are they happy with? 244 | * What are they disappointed with? 245 | 246 | -------------------------------------------------------------------------------- /mdbook-skill-tree/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /mdbook-skill-tree/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "addr2line" 5 | version = "0.12.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" 8 | dependencies = [ 9 | "gimli", 10 | ] 11 | 12 | [[package]] 13 | name = "aho-corasick" 14 | version = "0.7.10" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" 17 | dependencies = [ 18 | "memchr", 19 | ] 20 | 21 | [[package]] 22 | name = "ammonia" 23 | version = "3.1.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "89eac85170f4b3fb3dc5e442c1cfb036cb8eecf9dbbd431a161ffad15d90ea3b" 26 | dependencies = [ 27 | "html5ever", 28 | "lazy_static", 29 | "maplit", 30 | "markup5ever_rcdom", 31 | "matches", 32 | "tendril", 33 | "url 2.1.1", 34 | ] 35 | 36 | [[package]] 37 | name = "ansi_term" 38 | version = "0.11.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 41 | dependencies = [ 42 | "winapi 0.3.8", 43 | ] 44 | 45 | [[package]] 46 | name = "anyhow" 47 | version = "1.0.31" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" 50 | 51 | [[package]] 52 | name = "ascii" 53 | version = "0.9.3" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" 56 | 57 | [[package]] 58 | name = "atty" 59 | version = "0.2.14" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 62 | dependencies = [ 63 | "hermit-abi", 64 | "libc", 65 | "winapi 0.3.8", 66 | ] 67 | 68 | [[package]] 69 | name = "autocfg" 70 | version = "0.1.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 73 | 74 | [[package]] 75 | name = "autocfg" 76 | version = "1.0.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 79 | 80 | [[package]] 81 | name = "backtrace" 82 | version = "0.3.48" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" 85 | dependencies = [ 86 | "addr2line", 87 | "cfg-if", 88 | "libc", 89 | "object", 90 | "rustc-demangle", 91 | ] 92 | 93 | [[package]] 94 | name = "base64" 95 | version = "0.9.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" 98 | dependencies = [ 99 | "byteorder", 100 | "safemem", 101 | ] 102 | 103 | [[package]] 104 | name = "bitflags" 105 | version = "1.2.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 108 | 109 | [[package]] 110 | name = "block-buffer" 111 | version = "0.7.3" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 114 | dependencies = [ 115 | "block-padding", 116 | "byte-tools", 117 | "byteorder", 118 | "generic-array", 119 | ] 120 | 121 | [[package]] 122 | name = "block-padding" 123 | version = "0.1.5" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 126 | dependencies = [ 127 | "byte-tools", 128 | ] 129 | 130 | [[package]] 131 | name = "byte-tools" 132 | version = "0.3.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 135 | 136 | [[package]] 137 | name = "byteorder" 138 | version = "1.3.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 141 | 142 | [[package]] 143 | name = "bytes" 144 | version = "0.4.12" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 147 | dependencies = [ 148 | "byteorder", 149 | "iovec", 150 | ] 151 | 152 | [[package]] 153 | name = "cfg-if" 154 | version = "0.1.10" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 157 | 158 | [[package]] 159 | name = "chrono" 160 | version = "0.4.11" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" 163 | dependencies = [ 164 | "num-integer", 165 | "num-traits", 166 | "time", 167 | ] 168 | 169 | [[package]] 170 | name = "clap" 171 | version = "2.33.1" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" 174 | dependencies = [ 175 | "ansi_term", 176 | "atty", 177 | "bitflags", 178 | "strsim", 179 | "textwrap", 180 | "unicode-width", 181 | "vec_map", 182 | ] 183 | 184 | [[package]] 185 | name = "cloudabi" 186 | version = "0.0.3" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 189 | dependencies = [ 190 | "bitflags", 191 | ] 192 | 193 | [[package]] 194 | name = "combine" 195 | version = "3.8.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" 198 | dependencies = [ 199 | "ascii", 200 | "byteorder", 201 | "either", 202 | "memchr", 203 | "unreachable", 204 | ] 205 | 206 | [[package]] 207 | name = "darling" 208 | version = "0.8.6" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "9158d690bc62a3a57c3e45b85e4d50de2008b39345592c64efd79345c7e24be0" 211 | dependencies = [ 212 | "darling_core", 213 | "darling_macro", 214 | ] 215 | 216 | [[package]] 217 | name = "darling_core" 218 | version = "0.8.6" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "d2a368589465391e127e10c9e3a08efc8df66fd49b87dc8524c764bbe7f2ef82" 221 | dependencies = [ 222 | "fnv", 223 | "ident_case", 224 | "proc-macro2 0.4.30", 225 | "quote 0.6.13", 226 | "syn 0.15.44", 227 | ] 228 | 229 | [[package]] 230 | name = "darling_macro" 231 | version = "0.8.6" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "244e8987bd4e174385240cde20a3657f607fb0797563c28255c353b5819a07b1" 234 | dependencies = [ 235 | "darling_core", 236 | "quote 0.6.13", 237 | "syn 0.15.44", 238 | ] 239 | 240 | [[package]] 241 | name = "digest" 242 | version = "0.8.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 245 | dependencies = [ 246 | "generic-array", 247 | ] 248 | 249 | [[package]] 250 | name = "either" 251 | version = "1.5.3" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 254 | 255 | [[package]] 256 | name = "elasticlunr-rs" 257 | version = "2.3.9" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "35622eb004c8f0c5e7e2032815f3314a93df0db30a1ce5c94e62c1ecc81e22b9" 260 | dependencies = [ 261 | "lazy_static", 262 | "regex", 263 | "serde", 264 | "serde_derive", 265 | "serde_json", 266 | "strum", 267 | "strum_macros", 268 | ] 269 | 270 | [[package]] 271 | name = "env_logger" 272 | version = "0.6.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" 275 | dependencies = [ 276 | "atty", 277 | "humantime", 278 | "log 0.4.8", 279 | "regex", 280 | "termcolor", 281 | ] 282 | 283 | [[package]] 284 | name = "env_logger" 285 | version = "0.7.1" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 288 | dependencies = [ 289 | "atty", 290 | "humantime", 291 | "log 0.4.8", 292 | "regex", 293 | "termcolor", 294 | ] 295 | 296 | [[package]] 297 | name = "error-chain" 298 | version = "0.12.2" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" 301 | dependencies = [ 302 | "backtrace", 303 | "version_check 0.9.2", 304 | ] 305 | 306 | [[package]] 307 | name = "failure" 308 | version = "0.1.8" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 311 | dependencies = [ 312 | "backtrace", 313 | "failure_derive", 314 | ] 315 | 316 | [[package]] 317 | name = "failure_derive" 318 | version = "0.1.8" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 321 | dependencies = [ 322 | "proc-macro2 1.0.18", 323 | "quote 1.0.7", 324 | "syn 1.0.31", 325 | "synstructure", 326 | ] 327 | 328 | [[package]] 329 | name = "fake-simd" 330 | version = "0.1.2" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 333 | 334 | [[package]] 335 | name = "fehler" 336 | version = "1.0.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635" 339 | dependencies = [ 340 | "fehler-macros", 341 | ] 342 | 343 | [[package]] 344 | name = "fehler-macros" 345 | version = "1.0.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" 348 | dependencies = [ 349 | "proc-macro2 1.0.18", 350 | "quote 1.0.7", 351 | "syn 1.0.31", 352 | ] 353 | 354 | [[package]] 355 | name = "filetime" 356 | version = "0.2.10" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695" 359 | dependencies = [ 360 | "cfg-if", 361 | "libc", 362 | "redox_syscall", 363 | "winapi 0.3.8", 364 | ] 365 | 366 | [[package]] 367 | name = "fnv" 368 | version = "1.0.7" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 371 | 372 | [[package]] 373 | name = "fsevent" 374 | version = "0.4.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" 377 | dependencies = [ 378 | "bitflags", 379 | "fsevent-sys", 380 | ] 381 | 382 | [[package]] 383 | name = "fsevent-sys" 384 | version = "2.0.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" 387 | dependencies = [ 388 | "libc", 389 | ] 390 | 391 | [[package]] 392 | name = "fuchsia-cprng" 393 | version = "0.1.1" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 396 | 397 | [[package]] 398 | name = "fuchsia-zircon" 399 | version = "0.3.3" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 402 | dependencies = [ 403 | "bitflags", 404 | "fuchsia-zircon-sys", 405 | ] 406 | 407 | [[package]] 408 | name = "fuchsia-zircon-sys" 409 | version = "0.3.3" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 412 | 413 | [[package]] 414 | name = "futf" 415 | version = "0.1.4" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" 418 | dependencies = [ 419 | "mac", 420 | "new_debug_unreachable", 421 | ] 422 | 423 | [[package]] 424 | name = "generic-array" 425 | version = "0.12.3" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 428 | dependencies = [ 429 | "typenum", 430 | ] 431 | 432 | [[package]] 433 | name = "getopts" 434 | version = "0.2.21" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 437 | dependencies = [ 438 | "unicode-width", 439 | ] 440 | 441 | [[package]] 442 | name = "getrandom" 443 | version = "0.1.14" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 446 | dependencies = [ 447 | "cfg-if", 448 | "libc", 449 | "wasi", 450 | ] 451 | 452 | [[package]] 453 | name = "gimli" 454 | version = "0.21.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" 457 | 458 | [[package]] 459 | name = "gitignore" 460 | version = "1.0.6" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "f5beed3b526478bebc1dd164b7aa770ae709f918a7b978fcb4afdaf3bbee8dfd" 463 | dependencies = [ 464 | "glob", 465 | ] 466 | 467 | [[package]] 468 | name = "glob" 469 | version = "0.2.11" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" 472 | 473 | [[package]] 474 | name = "handlebars" 475 | version = "3.1.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "b37819efea9045a64bf9e40983ee62e20b4e9f0b14aa293e53dde8ac7c517f91" 478 | dependencies = [ 479 | "log 0.4.8", 480 | "pest", 481 | "pest_derive", 482 | "quick-error", 483 | "serde", 484 | "serde_json", 485 | ] 486 | 487 | [[package]] 488 | name = "heck" 489 | version = "0.3.1" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 492 | dependencies = [ 493 | "unicode-segmentation", 494 | ] 495 | 496 | [[package]] 497 | name = "hermit-abi" 498 | version = "0.1.13" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 501 | dependencies = [ 502 | "libc", 503 | ] 504 | 505 | [[package]] 506 | name = "html5ever" 507 | version = "0.25.1" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" 510 | dependencies = [ 511 | "log 0.4.8", 512 | "mac", 513 | "markup5ever", 514 | "proc-macro2 1.0.18", 515 | "quote 1.0.7", 516 | "syn 1.0.31", 517 | ] 518 | 519 | [[package]] 520 | name = "htmlescape" 521 | version = "0.3.1" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" 524 | 525 | [[package]] 526 | name = "httparse" 527 | version = "1.3.4" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 530 | 531 | [[package]] 532 | name = "humantime" 533 | version = "1.3.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 536 | dependencies = [ 537 | "quick-error", 538 | ] 539 | 540 | [[package]] 541 | name = "hyper" 542 | version = "0.10.16" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" 545 | dependencies = [ 546 | "base64", 547 | "httparse", 548 | "language-tags", 549 | "log 0.3.9", 550 | "mime", 551 | "num_cpus", 552 | "time", 553 | "traitobject", 554 | "typeable", 555 | "unicase 1.4.2", 556 | "url 1.7.2", 557 | ] 558 | 559 | [[package]] 560 | name = "ident_case" 561 | version = "1.0.1" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 564 | 565 | [[package]] 566 | name = "idna" 567 | version = "0.1.5" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 570 | dependencies = [ 571 | "matches", 572 | "unicode-bidi", 573 | "unicode-normalization", 574 | ] 575 | 576 | [[package]] 577 | name = "idna" 578 | version = "0.2.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 581 | dependencies = [ 582 | "matches", 583 | "unicode-bidi", 584 | "unicode-normalization", 585 | ] 586 | 587 | [[package]] 588 | name = "inotify" 589 | version = "0.7.1" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" 592 | dependencies = [ 593 | "bitflags", 594 | "inotify-sys", 595 | "libc", 596 | ] 597 | 598 | [[package]] 599 | name = "inotify-sys" 600 | version = "0.1.3" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" 603 | dependencies = [ 604 | "libc", 605 | ] 606 | 607 | [[package]] 608 | name = "iovec" 609 | version = "0.1.4" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 612 | dependencies = [ 613 | "libc", 614 | ] 615 | 616 | [[package]] 617 | name = "iron" 618 | version = "0.6.1" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "c6d308ca2d884650a8bf9ed2ff4cb13fbb2207b71f64cda11dc9b892067295e8" 621 | dependencies = [ 622 | "hyper", 623 | "log 0.3.9", 624 | "mime_guess", 625 | "modifier", 626 | "num_cpus", 627 | "plugin", 628 | "typemap", 629 | "url 1.7.2", 630 | ] 631 | 632 | [[package]] 633 | name = "is-match" 634 | version = "0.1.0" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "7e5b386aef33a1c677be65237cb9d32c3f3ef56bd035949710c4bb13083eb053" 637 | 638 | [[package]] 639 | name = "itertools" 640 | version = "0.8.2" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" 643 | dependencies = [ 644 | "either", 645 | ] 646 | 647 | [[package]] 648 | name = "itoa" 649 | version = "0.4.5" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 652 | 653 | [[package]] 654 | name = "kernel32-sys" 655 | version = "0.2.2" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 658 | dependencies = [ 659 | "winapi 0.2.8", 660 | "winapi-build", 661 | ] 662 | 663 | [[package]] 664 | name = "language-tags" 665 | version = "0.2.2" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 668 | 669 | [[package]] 670 | name = "lazy_static" 671 | version = "1.4.0" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 674 | 675 | [[package]] 676 | name = "lazycell" 677 | version = "1.2.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" 680 | 681 | [[package]] 682 | name = "libc" 683 | version = "0.2.71" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 686 | 687 | [[package]] 688 | name = "linked-hash-map" 689 | version = "0.5.3" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" 692 | 693 | [[package]] 694 | name = "log" 695 | version = "0.3.9" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 698 | dependencies = [ 699 | "log 0.4.8", 700 | ] 701 | 702 | [[package]] 703 | name = "log" 704 | version = "0.4.8" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 707 | dependencies = [ 708 | "cfg-if", 709 | ] 710 | 711 | [[package]] 712 | name = "mac" 713 | version = "0.1.1" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 716 | 717 | [[package]] 718 | name = "maplit" 719 | version = "1.0.2" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 722 | 723 | [[package]] 724 | name = "markup5ever" 725 | version = "0.10.0" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab" 728 | dependencies = [ 729 | "log 0.4.8", 730 | "phf 0.8.0", 731 | "phf_codegen 0.8.0", 732 | "serde", 733 | "serde_derive", 734 | "serde_json", 735 | "string_cache", 736 | "string_cache_codegen", 737 | "tendril", 738 | ] 739 | 740 | [[package]] 741 | name = "markup5ever_rcdom" 742 | version = "0.1.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b" 745 | dependencies = [ 746 | "html5ever", 747 | "markup5ever", 748 | "tendril", 749 | "xml5ever", 750 | ] 751 | 752 | [[package]] 753 | name = "matches" 754 | version = "0.1.8" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 757 | 758 | [[package]] 759 | name = "mdbook" 760 | version = "0.3.7" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "e7ec525f7ebccc2dd935c263717250cd37f9a4b264a77c5dbc950ea2734d8159" 763 | dependencies = [ 764 | "ammonia", 765 | "chrono", 766 | "clap", 767 | "elasticlunr-rs", 768 | "env_logger 0.6.2", 769 | "error-chain", 770 | "gitignore", 771 | "handlebars", 772 | "iron", 773 | "itertools", 774 | "lazy_static", 775 | "log 0.4.8", 776 | "memchr", 777 | "notify", 778 | "open", 779 | "pulldown-cmark 0.6.1", 780 | "regex", 781 | "serde", 782 | "serde_derive", 783 | "serde_json", 784 | "shlex", 785 | "staticfile", 786 | "tempfile", 787 | "toml", 788 | "toml-query", 789 | "ws", 790 | ] 791 | 792 | [[package]] 793 | name = "mdbook-skill-tree" 794 | version = "0.1.0" 795 | dependencies = [ 796 | "clap", 797 | "env_logger 0.7.1", 798 | "log 0.4.8", 799 | "mdbook", 800 | "pulldown-cmark 0.7.1", 801 | "pulldown-cmark-to-cmark", 802 | "regex", 803 | "rust-embed", 804 | "serde_json", 805 | "skill-tree", 806 | "toml_edit", 807 | ] 808 | 809 | [[package]] 810 | name = "memchr" 811 | version = "2.3.3" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 814 | 815 | [[package]] 816 | name = "mime" 817 | version = "0.2.6" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" 820 | dependencies = [ 821 | "log 0.3.9", 822 | ] 823 | 824 | [[package]] 825 | name = "mime_guess" 826 | version = "1.8.8" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3" 829 | dependencies = [ 830 | "mime", 831 | "phf 0.7.24", 832 | "phf_codegen 0.7.24", 833 | "unicase 1.4.2", 834 | ] 835 | 836 | [[package]] 837 | name = "mio" 838 | version = "0.6.22" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 841 | dependencies = [ 842 | "cfg-if", 843 | "fuchsia-zircon", 844 | "fuchsia-zircon-sys", 845 | "iovec", 846 | "kernel32-sys", 847 | "libc", 848 | "log 0.4.8", 849 | "miow", 850 | "net2", 851 | "slab", 852 | "winapi 0.2.8", 853 | ] 854 | 855 | [[package]] 856 | name = "mio-extras" 857 | version = "2.0.6" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" 860 | dependencies = [ 861 | "lazycell", 862 | "log 0.4.8", 863 | "mio", 864 | "slab", 865 | ] 866 | 867 | [[package]] 868 | name = "miow" 869 | version = "0.2.1" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 872 | dependencies = [ 873 | "kernel32-sys", 874 | "net2", 875 | "winapi 0.2.8", 876 | "ws2_32-sys", 877 | ] 878 | 879 | [[package]] 880 | name = "modifier" 881 | version = "0.1.0" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" 884 | 885 | [[package]] 886 | name = "mount" 887 | version = "0.4.0" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "e25c06012941aaf8c75f2eaf7ec5c48cf69f9fc489ab3eb3589edc107e386f0b" 890 | dependencies = [ 891 | "iron", 892 | "sequence_trie", 893 | ] 894 | 895 | [[package]] 896 | name = "net2" 897 | version = "0.2.34" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 900 | dependencies = [ 901 | "cfg-if", 902 | "libc", 903 | "winapi 0.3.8", 904 | ] 905 | 906 | [[package]] 907 | name = "new_debug_unreachable" 908 | version = "1.0.4" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 911 | 912 | [[package]] 913 | name = "notify" 914 | version = "4.0.15" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" 917 | dependencies = [ 918 | "bitflags", 919 | "filetime", 920 | "fsevent", 921 | "fsevent-sys", 922 | "inotify", 923 | "libc", 924 | "mio", 925 | "mio-extras", 926 | "walkdir", 927 | "winapi 0.3.8", 928 | ] 929 | 930 | [[package]] 931 | name = "num-integer" 932 | version = "0.1.42" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 935 | dependencies = [ 936 | "autocfg 1.0.0", 937 | "num-traits", 938 | ] 939 | 940 | [[package]] 941 | name = "num-traits" 942 | version = "0.2.11" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 945 | dependencies = [ 946 | "autocfg 1.0.0", 947 | ] 948 | 949 | [[package]] 950 | name = "num_cpus" 951 | version = "1.13.0" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 954 | dependencies = [ 955 | "hermit-abi", 956 | "libc", 957 | ] 958 | 959 | [[package]] 960 | name = "object" 961 | version = "0.19.0" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" 964 | 965 | [[package]] 966 | name = "opaque-debug" 967 | version = "0.2.3" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 970 | 971 | [[package]] 972 | name = "open" 973 | version = "1.4.0" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "7c283bf0114efea9e42f1a60edea9859e8c47528eae09d01df4b29c1e489cc48" 976 | dependencies = [ 977 | "winapi 0.3.8", 978 | ] 979 | 980 | [[package]] 981 | name = "percent-encoding" 982 | version = "1.0.1" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 985 | 986 | [[package]] 987 | name = "percent-encoding" 988 | version = "2.1.0" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 991 | 992 | [[package]] 993 | name = "pest" 994 | version = "2.1.3" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 997 | dependencies = [ 998 | "ucd-trie", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "pest_derive" 1003 | version = "2.1.0" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 1006 | dependencies = [ 1007 | "pest", 1008 | "pest_generator", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "pest_generator" 1013 | version = "2.1.3" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" 1016 | dependencies = [ 1017 | "pest", 1018 | "pest_meta", 1019 | "proc-macro2 1.0.18", 1020 | "quote 1.0.7", 1021 | "syn 1.0.31", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "pest_meta" 1026 | version = "2.1.3" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" 1029 | dependencies = [ 1030 | "maplit", 1031 | "pest", 1032 | "sha-1", 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "phf" 1037 | version = "0.7.24" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" 1040 | dependencies = [ 1041 | "phf_shared 0.7.24", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "phf" 1046 | version = "0.8.0" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 1049 | dependencies = [ 1050 | "phf_shared 0.8.0", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "phf_codegen" 1055 | version = "0.7.24" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" 1058 | dependencies = [ 1059 | "phf_generator 0.7.24", 1060 | "phf_shared 0.7.24", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "phf_codegen" 1065 | version = "0.8.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 1068 | dependencies = [ 1069 | "phf_generator 0.8.0", 1070 | "phf_shared 0.8.0", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "phf_generator" 1075 | version = "0.7.24" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" 1078 | dependencies = [ 1079 | "phf_shared 0.7.24", 1080 | "rand 0.6.5", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "phf_generator" 1085 | version = "0.8.0" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 1088 | dependencies = [ 1089 | "phf_shared 0.8.0", 1090 | "rand 0.7.3", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "phf_shared" 1095 | version = "0.7.24" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" 1098 | dependencies = [ 1099 | "siphasher 0.2.3", 1100 | "unicase 1.4.2", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "phf_shared" 1105 | version = "0.8.0" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 1108 | dependencies = [ 1109 | "siphasher 0.3.3", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "plugin" 1114 | version = "0.2.6" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" 1117 | dependencies = [ 1118 | "typemap", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "ppv-lite86" 1123 | version = "0.2.8" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 1126 | 1127 | [[package]] 1128 | name = "precomputed-hash" 1129 | version = "0.1.1" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 1132 | 1133 | [[package]] 1134 | name = "proc-macro-error" 1135 | version = "1.0.2" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" 1138 | dependencies = [ 1139 | "proc-macro-error-attr", 1140 | "proc-macro2 1.0.18", 1141 | "quote 1.0.7", 1142 | "syn 1.0.31", 1143 | "version_check 0.9.2", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "proc-macro-error-attr" 1148 | version = "1.0.2" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" 1151 | dependencies = [ 1152 | "proc-macro2 1.0.18", 1153 | "quote 1.0.7", 1154 | "syn 1.0.31", 1155 | "syn-mid", 1156 | "version_check 0.9.2", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "proc-macro2" 1161 | version = "0.4.30" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 1164 | dependencies = [ 1165 | "unicode-xid 0.1.0", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "proc-macro2" 1170 | version = "1.0.18" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 1173 | dependencies = [ 1174 | "unicode-xid 0.2.0", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "pulldown-cmark" 1179 | version = "0.6.1" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "1c205cc82214f3594e2d50686730314f817c67ffa80fe800cf0db78c3c2b9d9e" 1182 | dependencies = [ 1183 | "bitflags", 1184 | "getopts", 1185 | "memchr", 1186 | "unicase 2.6.0", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "pulldown-cmark" 1191 | version = "0.7.1" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "3e142c3b8f49d2200605ee6ba0b1d757310e9e7a72afe78c36ee2ef67300ee00" 1194 | dependencies = [ 1195 | "bitflags", 1196 | "getopts", 1197 | "memchr", 1198 | "unicase 2.6.0", 1199 | ] 1200 | 1201 | [[package]] 1202 | name = "pulldown-cmark-to-cmark" 1203 | version = "4.0.2" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "cffb594e453d29e238ac190362a4a291daec00396717a8d1670863121ac56958" 1206 | dependencies = [ 1207 | "pulldown-cmark 0.7.1", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "quick-error" 1212 | version = "1.2.3" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 1215 | 1216 | [[package]] 1217 | name = "quote" 1218 | version = "0.6.13" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 1221 | dependencies = [ 1222 | "proc-macro2 0.4.30", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "quote" 1227 | version = "1.0.7" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 1230 | dependencies = [ 1231 | "proc-macro2 1.0.18", 1232 | ] 1233 | 1234 | [[package]] 1235 | name = "rand" 1236 | version = "0.6.5" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 1239 | dependencies = [ 1240 | "autocfg 0.1.7", 1241 | "libc", 1242 | "rand_chacha 0.1.1", 1243 | "rand_core 0.4.2", 1244 | "rand_hc 0.1.0", 1245 | "rand_isaac", 1246 | "rand_jitter", 1247 | "rand_os", 1248 | "rand_pcg 0.1.2", 1249 | "rand_xorshift", 1250 | "winapi 0.3.8", 1251 | ] 1252 | 1253 | [[package]] 1254 | name = "rand" 1255 | version = "0.7.3" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1258 | dependencies = [ 1259 | "getrandom", 1260 | "libc", 1261 | "rand_chacha 0.2.2", 1262 | "rand_core 0.5.1", 1263 | "rand_hc 0.2.0", 1264 | "rand_pcg 0.2.1", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "rand_chacha" 1269 | version = "0.1.1" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 1272 | dependencies = [ 1273 | "autocfg 0.1.7", 1274 | "rand_core 0.3.1", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "rand_chacha" 1279 | version = "0.2.2" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1282 | dependencies = [ 1283 | "ppv-lite86", 1284 | "rand_core 0.5.1", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "rand_core" 1289 | version = "0.3.1" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 1292 | dependencies = [ 1293 | "rand_core 0.4.2", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "rand_core" 1298 | version = "0.4.2" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 1301 | 1302 | [[package]] 1303 | name = "rand_core" 1304 | version = "0.5.1" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1307 | dependencies = [ 1308 | "getrandom", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "rand_hc" 1313 | version = "0.1.0" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 1316 | dependencies = [ 1317 | "rand_core 0.3.1", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "rand_hc" 1322 | version = "0.2.0" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1325 | dependencies = [ 1326 | "rand_core 0.5.1", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "rand_isaac" 1331 | version = "0.1.1" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 1334 | dependencies = [ 1335 | "rand_core 0.3.1", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "rand_jitter" 1340 | version = "0.1.4" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 1343 | dependencies = [ 1344 | "libc", 1345 | "rand_core 0.4.2", 1346 | "winapi 0.3.8", 1347 | ] 1348 | 1349 | [[package]] 1350 | name = "rand_os" 1351 | version = "0.1.3" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 1354 | dependencies = [ 1355 | "cloudabi", 1356 | "fuchsia-cprng", 1357 | "libc", 1358 | "rand_core 0.4.2", 1359 | "rdrand", 1360 | "winapi 0.3.8", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "rand_pcg" 1365 | version = "0.1.2" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 1368 | dependencies = [ 1369 | "autocfg 0.1.7", 1370 | "rand_core 0.4.2", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "rand_pcg" 1375 | version = "0.2.1" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 1378 | dependencies = [ 1379 | "rand_core 0.5.1", 1380 | ] 1381 | 1382 | [[package]] 1383 | name = "rand_xorshift" 1384 | version = "0.1.1" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 1387 | dependencies = [ 1388 | "rand_core 0.3.1", 1389 | ] 1390 | 1391 | [[package]] 1392 | name = "rdrand" 1393 | version = "0.4.0" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1396 | dependencies = [ 1397 | "rand_core 0.3.1", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "redox_syscall" 1402 | version = "0.1.56" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 1405 | 1406 | [[package]] 1407 | name = "regex" 1408 | version = "1.3.9" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 1411 | dependencies = [ 1412 | "aho-corasick", 1413 | "memchr", 1414 | "regex-syntax", 1415 | "thread_local", 1416 | ] 1417 | 1418 | [[package]] 1419 | name = "regex-syntax" 1420 | version = "0.6.18" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 1423 | 1424 | [[package]] 1425 | name = "remove_dir_all" 1426 | version = "0.5.2" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 1429 | dependencies = [ 1430 | "winapi 0.3.8", 1431 | ] 1432 | 1433 | [[package]] 1434 | name = "rust-embed" 1435 | version = "5.5.1" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "3a17890cbd0fae97c2006fa1ecec9554946443c319f4dd8cd8d3b92031725161" 1438 | dependencies = [ 1439 | "rust-embed-impl", 1440 | "rust-embed-utils", 1441 | "walkdir", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "rust-embed-impl" 1446 | version = "5.5.1" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "60cacc306d294556771c6e92737ba7e6be0264144bc46dd713a14ef384b0d6b8" 1449 | dependencies = [ 1450 | "quote 1.0.7", 1451 | "rust-embed-utils", 1452 | "syn 1.0.31", 1453 | "walkdir", 1454 | ] 1455 | 1456 | [[package]] 1457 | name = "rust-embed-utils" 1458 | version = "5.0.0" 1459 | source = "registry+https://github.com/rust-lang/crates.io-index" 1460 | checksum = "97655158074ccb2d2cfb1ccb4c956ef0f4054e43a2c1e71146d4991e6961e105" 1461 | dependencies = [ 1462 | "walkdir", 1463 | ] 1464 | 1465 | [[package]] 1466 | name = "rustc-demangle" 1467 | version = "0.1.16" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 1470 | 1471 | [[package]] 1472 | name = "ryu" 1473 | version = "1.0.5" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1476 | 1477 | [[package]] 1478 | name = "safemem" 1479 | version = "0.3.3" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" 1482 | 1483 | [[package]] 1484 | name = "same-file" 1485 | version = "1.0.6" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1488 | dependencies = [ 1489 | "winapi-util", 1490 | ] 1491 | 1492 | [[package]] 1493 | name = "sequence_trie" 1494 | version = "0.3.6" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "1ee22067b7ccd072eeb64454b9c6e1b33b61cd0d49e895fd48676a184580e0c3" 1497 | 1498 | [[package]] 1499 | name = "serde" 1500 | version = "1.0.111" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" 1503 | 1504 | [[package]] 1505 | name = "serde_derive" 1506 | version = "1.0.111" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" 1509 | dependencies = [ 1510 | "proc-macro2 1.0.18", 1511 | "quote 1.0.7", 1512 | "syn 1.0.31", 1513 | ] 1514 | 1515 | [[package]] 1516 | name = "serde_json" 1517 | version = "1.0.55" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" 1520 | dependencies = [ 1521 | "itoa", 1522 | "ryu", 1523 | "serde", 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "sha-1" 1528 | version = "0.8.2" 1529 | source = "registry+https://github.com/rust-lang/crates.io-index" 1530 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" 1531 | dependencies = [ 1532 | "block-buffer", 1533 | "digest", 1534 | "fake-simd", 1535 | "opaque-debug", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "shlex" 1540 | version = "0.1.1" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 1543 | 1544 | [[package]] 1545 | name = "siphasher" 1546 | version = "0.2.3" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" 1549 | 1550 | [[package]] 1551 | name = "siphasher" 1552 | version = "0.3.3" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" 1555 | 1556 | [[package]] 1557 | name = "skill-tree" 1558 | version = "1.3.2" 1559 | dependencies = [ 1560 | "anyhow", 1561 | "clap", 1562 | "fehler", 1563 | "htmlescape", 1564 | "rust-embed", 1565 | "serde", 1566 | "serde_derive", 1567 | "structopt", 1568 | "svg", 1569 | "toml", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "slab" 1574 | version = "0.4.2" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1577 | 1578 | [[package]] 1579 | name = "smallvec" 1580 | version = "1.4.0" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" 1583 | 1584 | [[package]] 1585 | name = "staticfile" 1586 | version = "0.5.0" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "babd3fa68bb7e3994ce181c5f21ff3ff5fffef7b18b8a10163b45e4dafc6fb86" 1589 | dependencies = [ 1590 | "iron", 1591 | "mount", 1592 | "time", 1593 | "url 1.7.2", 1594 | ] 1595 | 1596 | [[package]] 1597 | name = "string_cache" 1598 | version = "0.8.0" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" 1601 | dependencies = [ 1602 | "lazy_static", 1603 | "new_debug_unreachable", 1604 | "phf_shared 0.8.0", 1605 | "precomputed-hash", 1606 | "serde", 1607 | ] 1608 | 1609 | [[package]] 1610 | name = "string_cache_codegen" 1611 | version = "0.5.1" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" 1614 | dependencies = [ 1615 | "phf_generator 0.8.0", 1616 | "phf_shared 0.8.0", 1617 | "proc-macro2 1.0.18", 1618 | "quote 1.0.7", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "strsim" 1623 | version = "0.8.0" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1626 | 1627 | [[package]] 1628 | name = "structopt" 1629 | version = "0.3.14" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" 1632 | dependencies = [ 1633 | "clap", 1634 | "lazy_static", 1635 | "structopt-derive", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "structopt-derive" 1640 | version = "0.4.7" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" 1643 | dependencies = [ 1644 | "heck", 1645 | "proc-macro-error", 1646 | "proc-macro2 1.0.18", 1647 | "quote 1.0.7", 1648 | "syn 1.0.31", 1649 | ] 1650 | 1651 | [[package]] 1652 | name = "strum" 1653 | version = "0.18.0" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" 1656 | 1657 | [[package]] 1658 | name = "strum_macros" 1659 | version = "0.18.0" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" 1662 | dependencies = [ 1663 | "heck", 1664 | "proc-macro2 1.0.18", 1665 | "quote 1.0.7", 1666 | "syn 1.0.31", 1667 | ] 1668 | 1669 | [[package]] 1670 | name = "svg" 1671 | version = "0.5.12" 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" 1673 | checksum = "a863ec1f8e7cfd4ea449f77445cca06aac240b9a677ccf12b0f65ef020db52c7" 1674 | 1675 | [[package]] 1676 | name = "syn" 1677 | version = "0.15.44" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 1680 | dependencies = [ 1681 | "proc-macro2 0.4.30", 1682 | "quote 0.6.13", 1683 | "unicode-xid 0.1.0", 1684 | ] 1685 | 1686 | [[package]] 1687 | name = "syn" 1688 | version = "1.0.31" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" 1691 | dependencies = [ 1692 | "proc-macro2 1.0.18", 1693 | "quote 1.0.7", 1694 | "unicode-xid 0.2.0", 1695 | ] 1696 | 1697 | [[package]] 1698 | name = "syn-mid" 1699 | version = "0.5.0" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" 1702 | dependencies = [ 1703 | "proc-macro2 1.0.18", 1704 | "quote 1.0.7", 1705 | "syn 1.0.31", 1706 | ] 1707 | 1708 | [[package]] 1709 | name = "synstructure" 1710 | version = "0.12.4" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" 1713 | dependencies = [ 1714 | "proc-macro2 1.0.18", 1715 | "quote 1.0.7", 1716 | "syn 1.0.31", 1717 | "unicode-xid 0.2.0", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "tempfile" 1722 | version = "3.1.0" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 1725 | dependencies = [ 1726 | "cfg-if", 1727 | "libc", 1728 | "rand 0.7.3", 1729 | "redox_syscall", 1730 | "remove_dir_all", 1731 | "winapi 0.3.8", 1732 | ] 1733 | 1734 | [[package]] 1735 | name = "tendril" 1736 | version = "0.4.1" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b" 1739 | dependencies = [ 1740 | "futf", 1741 | "mac", 1742 | "utf-8", 1743 | ] 1744 | 1745 | [[package]] 1746 | name = "termcolor" 1747 | version = "1.1.0" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 1750 | dependencies = [ 1751 | "winapi-util", 1752 | ] 1753 | 1754 | [[package]] 1755 | name = "textwrap" 1756 | version = "0.11.0" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1759 | dependencies = [ 1760 | "unicode-width", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "thread_local" 1765 | version = "1.0.1" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1768 | dependencies = [ 1769 | "lazy_static", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "time" 1774 | version = "0.1.43" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1777 | dependencies = [ 1778 | "libc", 1779 | "winapi 0.3.8", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "toml" 1784 | version = "0.5.6" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 1787 | dependencies = [ 1788 | "serde", 1789 | ] 1790 | 1791 | [[package]] 1792 | name = "toml-query" 1793 | version = "0.9.2" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "654d5afba116c445bb5fb6812e7c3177d90d143427af73f12956f33e18a1cedb" 1796 | dependencies = [ 1797 | "failure", 1798 | "failure_derive", 1799 | "is-match", 1800 | "lazy_static", 1801 | "regex", 1802 | "toml", 1803 | "toml-query_derive", 1804 | ] 1805 | 1806 | [[package]] 1807 | name = "toml-query_derive" 1808 | version = "0.9.2" 1809 | source = "registry+https://github.com/rust-lang/crates.io-index" 1810 | checksum = "528baacc7fbc5e12b3fc32f483bea1b1cf531afa71cfaae54838d7095a6add9b" 1811 | dependencies = [ 1812 | "darling", 1813 | "quote 0.6.13", 1814 | "syn 0.15.44", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "toml_edit" 1819 | version = "0.1.5" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "87f53b1aca7d5fe2e17498a38cac0e1f5a33234d5b980fb36b9402bb93b98ae4" 1822 | dependencies = [ 1823 | "chrono", 1824 | "combine", 1825 | "linked-hash-map", 1826 | ] 1827 | 1828 | [[package]] 1829 | name = "traitobject" 1830 | version = "0.1.0" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" 1833 | 1834 | [[package]] 1835 | name = "typeable" 1836 | version = "0.1.2" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 1839 | 1840 | [[package]] 1841 | name = "typemap" 1842 | version = "0.3.3" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" 1845 | dependencies = [ 1846 | "unsafe-any", 1847 | ] 1848 | 1849 | [[package]] 1850 | name = "typenum" 1851 | version = "1.12.0" 1852 | source = "registry+https://github.com/rust-lang/crates.io-index" 1853 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 1854 | 1855 | [[package]] 1856 | name = "ucd-trie" 1857 | version = "0.1.3" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 1860 | 1861 | [[package]] 1862 | name = "unicase" 1863 | version = "1.4.2" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 1866 | dependencies = [ 1867 | "version_check 0.1.5", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "unicase" 1872 | version = "2.6.0" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1875 | dependencies = [ 1876 | "version_check 0.9.2", 1877 | ] 1878 | 1879 | [[package]] 1880 | name = "unicode-bidi" 1881 | version = "0.3.4" 1882 | source = "registry+https://github.com/rust-lang/crates.io-index" 1883 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1884 | dependencies = [ 1885 | "matches", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "unicode-normalization" 1890 | version = "0.1.12" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" 1893 | dependencies = [ 1894 | "smallvec", 1895 | ] 1896 | 1897 | [[package]] 1898 | name = "unicode-segmentation" 1899 | version = "1.6.0" 1900 | source = "registry+https://github.com/rust-lang/crates.io-index" 1901 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 1902 | 1903 | [[package]] 1904 | name = "unicode-width" 1905 | version = "0.1.7" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 1908 | 1909 | [[package]] 1910 | name = "unicode-xid" 1911 | version = "0.1.0" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 1914 | 1915 | [[package]] 1916 | name = "unicode-xid" 1917 | version = "0.2.0" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 1920 | 1921 | [[package]] 1922 | name = "unreachable" 1923 | version = "1.0.0" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 1926 | dependencies = [ 1927 | "void", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "unsafe-any" 1932 | version = "0.4.2" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" 1935 | dependencies = [ 1936 | "traitobject", 1937 | ] 1938 | 1939 | [[package]] 1940 | name = "url" 1941 | version = "1.7.2" 1942 | source = "registry+https://github.com/rust-lang/crates.io-index" 1943 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 1944 | dependencies = [ 1945 | "idna 0.1.5", 1946 | "matches", 1947 | "percent-encoding 1.0.1", 1948 | ] 1949 | 1950 | [[package]] 1951 | name = "url" 1952 | version = "2.1.1" 1953 | source = "registry+https://github.com/rust-lang/crates.io-index" 1954 | checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 1955 | dependencies = [ 1956 | "idna 0.2.0", 1957 | "matches", 1958 | "percent-encoding 2.1.0", 1959 | ] 1960 | 1961 | [[package]] 1962 | name = "utf-8" 1963 | version = "0.7.5" 1964 | source = "registry+https://github.com/rust-lang/crates.io-index" 1965 | checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" 1966 | 1967 | [[package]] 1968 | name = "vec_map" 1969 | version = "0.8.2" 1970 | source = "registry+https://github.com/rust-lang/crates.io-index" 1971 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1972 | 1973 | [[package]] 1974 | name = "version_check" 1975 | version = "0.1.5" 1976 | source = "registry+https://github.com/rust-lang/crates.io-index" 1977 | checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 1978 | 1979 | [[package]] 1980 | name = "version_check" 1981 | version = "0.9.2" 1982 | source = "registry+https://github.com/rust-lang/crates.io-index" 1983 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1984 | 1985 | [[package]] 1986 | name = "void" 1987 | version = "1.0.2" 1988 | source = "registry+https://github.com/rust-lang/crates.io-index" 1989 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1990 | 1991 | [[package]] 1992 | name = "walkdir" 1993 | version = "2.3.1" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1996 | dependencies = [ 1997 | "same-file", 1998 | "winapi 0.3.8", 1999 | "winapi-util", 2000 | ] 2001 | 2002 | [[package]] 2003 | name = "wasi" 2004 | version = "0.9.0+wasi-snapshot-preview1" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 2007 | 2008 | [[package]] 2009 | name = "winapi" 2010 | version = "0.2.8" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 2013 | 2014 | [[package]] 2015 | name = "winapi" 2016 | version = "0.3.8" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 2019 | dependencies = [ 2020 | "winapi-i686-pc-windows-gnu", 2021 | "winapi-x86_64-pc-windows-gnu", 2022 | ] 2023 | 2024 | [[package]] 2025 | name = "winapi-build" 2026 | version = "0.1.1" 2027 | source = "registry+https://github.com/rust-lang/crates.io-index" 2028 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 2029 | 2030 | [[package]] 2031 | name = "winapi-i686-pc-windows-gnu" 2032 | version = "0.4.0" 2033 | source = "registry+https://github.com/rust-lang/crates.io-index" 2034 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2035 | 2036 | [[package]] 2037 | name = "winapi-util" 2038 | version = "0.1.5" 2039 | source = "registry+https://github.com/rust-lang/crates.io-index" 2040 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2041 | dependencies = [ 2042 | "winapi 0.3.8", 2043 | ] 2044 | 2045 | [[package]] 2046 | name = "winapi-x86_64-pc-windows-gnu" 2047 | version = "0.4.0" 2048 | source = "registry+https://github.com/rust-lang/crates.io-index" 2049 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2050 | 2051 | [[package]] 2052 | name = "ws" 2053 | version = "0.9.1" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "c51a2c47b5798ccc774ffb93ff536aec7c4275d722fd9c740c83cdd1af1f2d94" 2056 | dependencies = [ 2057 | "byteorder", 2058 | "bytes", 2059 | "httparse", 2060 | "log 0.4.8", 2061 | "mio", 2062 | "mio-extras", 2063 | "rand 0.7.3", 2064 | "sha-1", 2065 | "slab", 2066 | "url 2.1.1", 2067 | ] 2068 | 2069 | [[package]] 2070 | name = "ws2_32-sys" 2071 | version = "0.2.1" 2072 | source = "registry+https://github.com/rust-lang/crates.io-index" 2073 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 2074 | dependencies = [ 2075 | "winapi 0.2.8", 2076 | "winapi-build", 2077 | ] 2078 | 2079 | [[package]] 2080 | name = "xml5ever" 2081 | version = "0.16.1" 2082 | source = "registry+https://github.com/rust-lang/crates.io-index" 2083 | checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" 2084 | dependencies = [ 2085 | "log 0.4.8", 2086 | "mac", 2087 | "markup5ever", 2088 | "time", 2089 | ] 2090 | -------------------------------------------------------------------------------- /mdbook-skill-tree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mdbook-skill-tree" 3 | version = "3.0.0" 4 | authors = ["Niko Matsakis "] 5 | edition = "2018" 6 | description = "mdbook plugin to show roadmaps" 7 | license = "MIT" 8 | repository = "https://github.com/nikomatsakis/skill-tree" 9 | homepage = "https://github.com/nikomatsakis/skill-tree" 10 | 11 | [dependencies] 12 | anyhow = "1.0" 13 | skill-tree = { version="3.0.0", path=".." } 14 | mdbook = "0.4.7" 15 | pulldown-cmark = "0.7.0" 16 | pulldown-cmark-to-cmark = "4.0.0" 17 | env_logger = "0.7.1" 18 | log = "0.4" 19 | clap = "2.33" 20 | serde_json = "1.0" 21 | toml_edit = "0.1.5" 22 | fehler = "1.0.0-alpha.2" 23 | 24 | [dev-dependencies] 25 | regex = "1.0" -------------------------------------------------------------------------------- /mdbook-skill-tree/js/README.md: -------------------------------------------------------------------------------- 1 | View the skill-tree by loading `skill-tree.html` in this directory! 2 | 3 | viz-js sources downloaded from 4 | 5 | panzoom sources downloaded from : 6 | 7 | -------------------------------------------------------------------------------- /mdbook-skill-tree/js/panzoom.min.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.panzoom=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i0){transform.x+=diff;adjusted=true}diff=boundingBox.right-clientRect.left;if(diff<0){transform.x+=diff;adjusted=true}diff=boundingBox.top-clientRect.bottom;if(diff>0){transform.y+=diff;adjusted=true}diff=boundingBox.bottom-clientRect.top;if(diff<0){transform.y+=diff;adjusted=true}return adjusted}function getBoundingBox(){if(!bounds)return;if(typeof bounds==="boolean"){var ownerRect=owner.getBoundingClientRect();var sceneWidth=ownerRect.width;var sceneHeight=ownerRect.height;return{left:sceneWidth*boundsPadding,top:sceneHeight*boundsPadding,right:sceneWidth*(1-boundsPadding),bottom:sceneHeight*(1-boundsPadding)}}return bounds}function getClientRect(){var bbox=panController.getBBox();var leftTop=client(bbox.left,bbox.top);return{left:leftTop.x,top:leftTop.y,right:bbox.width*transform.scale+leftTop.x,bottom:bbox.height*transform.scale+leftTop.y}}function client(x,y){return{x:x*transform.scale+transform.x,y:y*transform.scale+transform.y}}function makeDirty(){isDirty=true;frameAnimation=window.requestAnimationFrame(frame)}function zoomByRatio(clientX,clientY,ratio){if(isNaN(clientX)||isNaN(clientY)||isNaN(ratio)){throw new Error("zoom requires valid numbers")}var newScale=transform.scale*ratio;if(newScalemaxZoom){if(transform.scale===maxZoom)return;ratio=maxZoom/transform.scale}var size=transformToScreen(clientX,clientY);transform.x=size.x-ratio*(size.x-transform.x);transform.y=size.y-ratio*(size.y-transform.y);if(bounds&&boundsPadding===1&&minZoom===1){transform.scale*=ratio;keepTransformInsideBounds()}else{var transformAdjusted=keepTransformInsideBounds();if(!transformAdjusted)transform.scale*=ratio}triggerEvent("zoom");makeDirty()}function zoomAbs(clientX,clientY,zoomLevel){var ratio=zoomLevel/transform.scale;zoomByRatio(clientX,clientY,ratio)}function centerOn(ui){var parent=ui.ownerSVGElement;if(!parent)throw new Error("ui element is required to be within the scene");var clientRect=ui.getBoundingClientRect();var cx=clientRect.left+clientRect.width/2;var cy=clientRect.top+clientRect.height/2;var container=parent.getBoundingClientRect();var dx=container.width/2-cx;var dy=container.height/2-cy;internalMoveBy(dx,dy,true)}function smoothMoveTo(x,y){internalMoveBy(x-transform.x,y-transform.y,true)}function internalMoveBy(dx,dy,smooth){if(!smooth){return moveBy(dx,dy)}if(moveByAnimation)moveByAnimation.cancel();var from={x:0,y:0};var to={x:dx,y:dy};var lastX=0;var lastY=0;moveByAnimation=animate(from,to,{step:function(v){moveBy(v.x-lastX,v.y-lastY);lastX=v.x;lastY=v.y}})}function scroll(x,y){cancelZoomAnimation();moveTo(x,y)}function dispose(){releaseEvents()}function listenForEvents(){owner.addEventListener("mousedown",onMouseDown,{passive:false});owner.addEventListener("dblclick",onDoubleClick,{passive:false});owner.addEventListener("touchstart",onTouch,{passive:false});owner.addEventListener("keydown",onKeyDown,{passive:false});wheel.addWheelListener(owner,onMouseWheel,{passive:false});makeDirty()}function releaseEvents(){wheel.removeWheelListener(owner,onMouseWheel);owner.removeEventListener("mousedown",onMouseDown);owner.removeEventListener("keydown",onKeyDown);owner.removeEventListener("dblclick",onDoubleClick);owner.removeEventListener("touchstart",onTouch);if(frameAnimation){window.cancelAnimationFrame(frameAnimation);frameAnimation=0}smoothScroll.cancel();releaseDocumentMouse();releaseTouches();textSelection.release();triggerPanEnd()}function frame(){if(isDirty)applyTransform()}function applyTransform(){isDirty=false;panController.applyTransform(transform);triggerEvent("transform");frameAnimation=0}function onKeyDown(e){var x=0,y=0,z=0;if(e.keyCode===38){y=1}else if(e.keyCode===40){y=-1}else if(e.keyCode===37){x=1}else if(e.keyCode===39){x=-1}else if(e.keyCode===189||e.keyCode===109){z=1}else if(e.keyCode===187||e.keyCode===107){z=-1}if(filterKey(e,x,y,z)){return}if(x||y){e.preventDefault();e.stopPropagation();var clientRect=owner.getBoundingClientRect();var offset=Math.min(clientRect.width,clientRect.height);var moveSpeedRatio=.05;var dx=offset*moveSpeedRatio*x;var dy=offset*moveSpeedRatio*y;internalMoveBy(dx,dy)}if(z){var scaleMultiplier=getScaleMultiplier(z*100);var offset=transformOrigin?getTransformOriginOffset():midPoint();publicZoomTo(offset.x,offset.y,scaleMultiplier)}}function midPoint(){var ownerRect=owner.getBoundingClientRect();return{x:ownerRect.width/2,y:ownerRect.height/2}}function onTouch(e){beforeTouch(e);if(e.touches.length===1){return handleSingleFingerTouch(e,e.touches[0])}else if(e.touches.length===2){pinchZoomLength=getPinchZoomLength(e.touches[0],e.touches[1]);multiTouch=true;startTouchListenerIfNeeded()}}function beforeTouch(e){if(options.onTouch&&!options.onTouch(e)){return}e.stopPropagation();e.preventDefault()}function beforeDoubleClick(e){if(options.onDoubleClick&&!options.onDoubleClick(e)){return}e.preventDefault();e.stopPropagation()}function handleSingleFingerTouch(e){var touch=e.touches[0];var offset=getOffsetXY(touch);lastSingleFingerOffset=offset;var point=transformToScreen(offset.x,offset.y);mouseX=point.x;mouseY=point.y;smoothScroll.cancel();startTouchListenerIfNeeded()}function startTouchListenerIfNeeded(){if(touchInProgress){return}touchInProgress=true;document.addEventListener("touchmove",handleTouchMove);document.addEventListener("touchend",handleTouchEnd);document.addEventListener("touchcancel",handleTouchEnd)}function handleTouchMove(e){if(e.touches.length===1){e.stopPropagation();var touch=e.touches[0];var offset=getOffsetXY(touch);var point=transformToScreen(offset.x,offset.y);var dx=point.x-mouseX;var dy=point.y-mouseY;if(dx!==0&&dy!==0){triggerPanStart()}mouseX=point.x;mouseY=point.y;internalMoveBy(dx,dy)}else if(e.touches.length===2){multiTouch=true;var t1=e.touches[0];var t2=e.touches[1];var currentPinchLength=getPinchZoomLength(t1,t2);var scaleMultiplier=1+(currentPinchLength/pinchZoomLength-1)*pinchSpeed;var firstTouchPoint=getOffsetXY(t1);var secondTouchPoint=getOffsetXY(t2);mouseX=(firstTouchPoint.x+secondTouchPoint.x)/2;mouseY=(firstTouchPoint.y+secondTouchPoint.y)/2;if(transformOrigin){var offset=getTransformOriginOffset();mouseX=offset.x;mouseY=offset.y}publicZoomTo(mouseX,mouseY,scaleMultiplier);pinchZoomLength=currentPinchLength;e.stopPropagation();e.preventDefault()}}function handleTouchEnd(e){if(e.touches.length>0){var offset=getOffsetXY(e.touches[0]);var point=transformToScreen(offset.x,offset.y);mouseX=point.x;mouseY=point.y}else{var now=new Date;if(now-lastTouchEndTime0)delta*=100;var scaleMultiplier=getScaleMultiplier(delta);if(scaleMultiplier!==1){var offset=transformOrigin?getTransformOriginOffset():getOffsetXY(e);publicZoomTo(offset.x,offset.y,scaleMultiplier);e.preventDefault()}}function getOffsetXY(e){var offsetX,offsetY;var ownerRect=owner.getBoundingClientRect();offsetX=e.clientX-ownerRect.left;offsetY=e.clientY-ownerRect.top;return{x:offsetX,y:offsetY}}function smoothZoom(clientX,clientY,scaleMultiplier){var fromValue=transform.scale;var from={scale:fromValue};var to={scale:scaleMultiplier*fromValue};smoothScroll.cancel();cancelZoomAnimation();zoomToAnimation=animate(from,to,{step:function(v){zoomAbs(clientX,clientY,v.scale)},done:triggerZoomEnd})}function smoothZoomAbs(clientX,clientY,toScaleValue){var fromValue=transform.scale;var from={scale:fromValue};var to={scale:toScaleValue};smoothScroll.cancel();cancelZoomAnimation();zoomToAnimation=animate(from,to,{step:function(v){zoomAbs(clientX,clientY,v.scale)}})}function getTransformOriginOffset(){var ownerRect=owner.getBoundingClientRect();return{x:ownerRect.width*transformOrigin.x,y:ownerRect.height*transformOrigin.y}}function publicZoomTo(clientX,clientY,scaleMultiplier){smoothScroll.cancel();cancelZoomAnimation();return zoomByRatio(clientX,clientY,scaleMultiplier)}function cancelZoomAnimation(){if(zoomToAnimation){zoomToAnimation.cancel();zoomToAnimation=null}}function getScaleMultiplier(delta){var sign=Math.sign(delta);var deltaAdjustedSpeed=Math.min(.25,Math.abs(speed*delta/128));return 1-sign*deltaAdjustedSpeed}function triggerPanStart(){if(!panstartFired){triggerEvent("panstart");panstartFired=true;smoothScroll.start()}}function triggerPanEnd(){if(panstartFired){if(!multiTouch)smoothScroll.stop();triggerEvent("panend")}}function triggerZoomEnd(){triggerEvent("zoomend")}function triggerEvent(name){api.fire(name,api)}}function parseTransformOrigin(options){if(!options)return;if(typeof options==="object"){if(!isNumber(options.x)||!isNumber(options.y))failTransformOrigin(options);return options}failTransformOrigin()}function failTransformOrigin(options){console.error(options);throw new Error(["Cannot parse transform origin.","Some good examples:",' "center center" can be achieved with {x: 0.5, y: 0.5}',' "top center" can be achieved with {x: 0.5, y: 0}',' "bottom right" can be achieved with {x: 1, y: 1}'].join("\n"))}function noop(){}function validateBounds(bounds){var boundsType=typeof bounds;if(boundsType==="undefined"||boundsType==="boolean")return;var validBounds=isNumber(bounds.left)&&isNumber(bounds.top)&&isNumber(bounds.bottom)&&isNumber(bounds.right);if(!validBounds)throw new Error("Bounds object is not valid. It can be: "+"undefined, boolean (true|false) or an object {left, top, right, bottom}")}function isNumber(x){return Number.isFinite(x)}function isNaN(value){if(Number.isNaN){return Number.isNaN(value)}return value!==value}function rigidScroll(){return{start:noop,stop:noop,cancel:noop}}function autoRun(){if(typeof document==="undefined")return;var scripts=document.getElementsByTagName("script");if(!scripts)return;var panzoomScript;for(var i=0;iminVelocity){ax=amplitude*vx;targetX+=ax}if(vy<-minVelocity||vy>minVelocity){ay=amplitude*vy;targetY+=ay}raf=requestAnimationFrame(autoScroll)}function autoScroll(){var elapsed=Date.now()-timestamp;var moving=false;var dx=0;var dy=0;if(ax){dx=-ax*Math.exp(-elapsed/timeConstant);if(dx>.5||dx<-.5)moving=true;else dx=ax=0}if(ay){dy=-ay*Math.exp(-elapsed/timeConstant);if(dy>.5||dy<-.5)moving=true;else dy=ay=0}if(moving){scroll(targetX+dx,targetY+dy);raf=requestAnimationFrame(autoScroll)}}}function getCancelAnimationFrame(){if(typeof cancelAnimationFrame==="function")return cancelAnimationFrame;return clearTimeout}function getRequestAnimationFrame(){if(typeof requestAnimationFrame==="function")return requestAnimationFrame;return function(handler){return setTimeout(handler,16)}}},{}],5:[function(require,module,exports){module.exports=makeSvgController;module.exports.canAttach=isSVGElement;function makeSvgController(svgElement,options){if(!isSVGElement(svgElement)){throw new Error("svg element is required for svg.panzoom to work")}var owner=svgElement.ownerSVGElement;if(!owner){throw new Error("Do not apply panzoom to the root element. "+"Use its child instead (e.g. ). "+"As of March 2016 only FireFox supported transform on the root element")}if(!options.disableKeyboardInteraction){owner.setAttribute("tabindex",0)}var api={getBBox:getBBox,getScreenCTM:getScreenCTM,getOwner:getOwner,applyTransform:applyTransform,initTransform:initTransform};return api;function getOwner(){return owner}function getBBox(){var bbox=svgElement.getBBox();return{left:bbox.x,top:bbox.y,width:bbox.width,height:bbox.height}}function getScreenCTM(){var ctm=owner.getCTM();if(!ctm){return owner.getScreenCTM()}return ctm}function initTransform(transform){var screenCTM=svgElement.getCTM();if(screenCTM===null){screenCTM=document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGMatrix()}transform.x=screenCTM.e;transform.y=screenCTM.f;transform.scale=screenCTM.a;owner.removeAttributeNS(null,"viewBox")}function applyTransform(transform){svgElement.setAttribute("transform","matrix("+transform.scale+" 0 0 "+transform.scale+" "+transform.x+" "+transform.y+")")}}function isSVGElement(element){return element&&element.ownerSVGElement&&element.getCTM}},{}],6:[function(require,module,exports){module.exports=Transform;function Transform(){this.x=0;this.y=0;this.scale=1}},{}],7:[function(require,module,exports){var BezierEasing=require("bezier-easing");var animations={ease:BezierEasing(.25,.1,.25,1),easeIn:BezierEasing(.42,0,1,1),easeOut:BezierEasing(0,0,.58,1),easeInOut:BezierEasing(.42,0,.58,1),linear:BezierEasing(0,0,1,1)};module.exports=animate;module.exports.makeAggregateRaf=makeAggregateRaf;module.exports.sharedScheduler=makeAggregateRaf();function animate(source,target,options){var start=Object.create(null);var diff=Object.create(null);options=options||{};var easing=typeof options.easing==="function"?options.easing:animations[options.easing];if(!easing){if(options.easing){console.warn("Unknown easing function in amator: "+options.easing)}easing=animations.ease}var step=typeof options.step==="function"?options.step:noop;var done=typeof options.done==="function"?options.done:noop;var scheduler=getScheduler(options.scheduler);var keys=Object.keys(target);keys.forEach(function(key){start[key]=source[key];diff[key]=target[key]-source[key]});var durationInMs=typeof options.duration==="number"?options.duration:400;var durationInFrames=Math.max(1,durationInMs*.06);var previousAnimationId;var frame=0;previousAnimationId=scheduler.next(loop);return{cancel:cancel};function cancel(){scheduler.cancel(previousAnimationId);previousAnimationId=0}function loop(){var t=easing(frame/durationInFrames);frame+=1;setValues(t);if(frame<=durationInFrames){previousAnimationId=scheduler.next(loop);step(source)}else{previousAnimationId=0;setTimeout(function(){done(source)},0)}}function setValues(t){keys.forEach(function(key){source[key]=diff[key]*t+start[key]})}}function noop(){}function getScheduler(scheduler){if(!scheduler){var canRaf=typeof window!=="undefined"&&window.requestAnimationFrame;return canRaf?rafScheduler():timeoutScheduler()}if(typeof scheduler.next!=="function")throw new Error("Scheduler is supposed to have next(cb) function");if(typeof scheduler.cancel!=="function")throw new Error("Scheduler is supposed to have cancel(handle) function");return scheduler}function rafScheduler(){return{next:window.requestAnimationFrame.bind(window),cancel:window.cancelAnimationFrame.bind(window)}}function timeoutScheduler(){return{next:function(cb){return setTimeout(cb,1e3/60)},cancel:function(id){return clearTimeout(id)}}}function makeAggregateRaf(){var frontBuffer=new Set;var backBuffer=new Set;var frameToken=0;return{next:next,cancel:next,clearAll:clearAll};function clearAll(){frontBuffer.clear();backBuffer.clear();cancelAnimationFrame(frameToken);frameToken=0}function next(callback){backBuffer.add(callback);renderNextFrame()}function renderNextFrame(){if(!frameToken)frameToken=requestAnimationFrame(renderFrame)}function renderFrame(){frameToken=0;var t=backBuffer;backBuffer=frontBuffer;frontBuffer=t;frontBuffer.forEach(function(callback){callback()});frontBuffer.clear()}function cancel(callback){backBuffer.delete(callback)}}},{"bezier-easing":8}],8:[function(require,module,exports){var NEWTON_ITERATIONS=4;var NEWTON_MIN_SLOPE=.001;var SUBDIVISION_PRECISION=1e-7;var SUBDIVISION_MAX_ITERATIONS=10;var kSplineTableSize=11;var kSampleStepSize=1/(kSplineTableSize-1);var float32ArraySupported=typeof Float32Array==="function";function A(aA1,aA2){return 1-3*aA2+3*aA1}function B(aA1,aA2){return 3*aA2-6*aA1}function C(aA1){return 3*aA1}function calcBezier(aT,aA1,aA2){return((A(aA1,aA2)*aT+B(aA1,aA2))*aT+C(aA1))*aT}function getSlope(aT,aA1,aA2){return 3*A(aA1,aA2)*aT*aT+2*B(aA1,aA2)*aT+C(aA1)}function binarySubdivide(aX,aA,aB,mX1,mX2){var currentX,currentT,i=0;do{currentT=aA+(aB-aA)/2;currentX=calcBezier(currentT,mX1,mX2)-aX;if(currentX>0){aB=currentT}else{aA=currentT}}while(Math.abs(currentX)>SUBDIVISION_PRECISION&&++i=NEWTON_MIN_SLOPE){return newtonRaphsonIterate(aX,guessForT,mX1,mX2)}else if(initialSlope===0){return guessForT}else{return binarySubdivide(aX,intervalStart,intervalStart+kSampleStepSize,mX1,mX2)}}return function BezierEasing(x){if(x===0){return 0}if(x===1){return 1}return calcBezier(getTForX(x),mY1,mY2)}}},{}],9:[function(require,module,exports){module.exports=function eventify(subject){validateSubject(subject);var eventsStorage=createEventsStorage(subject);subject.on=eventsStorage.on;subject.off=eventsStorage.off;subject.fire=eventsStorage.fire;return subject};function createEventsStorage(subject){var registeredEvents=Object.create(null);return{on:function(eventName,callback,ctx){if(typeof callback!=="function"){throw new Error("callback is expected to be a function")}var handlers=registeredEvents[eventName];if(!handlers){handlers=registeredEvents[eventName]=[]}handlers.push({callback:callback,ctx:ctx});return subject},off:function(eventName,callback){var wantToRemoveAll=typeof eventName==="undefined";if(wantToRemoveAll){registeredEvents=Object.create(null);return subject}if(registeredEvents[eventName]){var deleteAllCallbacksForEvent=typeof callback!=="function";if(deleteAllCallbacksForEvent){delete registeredEvents[eventName]}else{var callbacks=registeredEvents[eventName];for(var i=0;i1){fireArguments=Array.prototype.splice.call(arguments,1)}for(var i=0;i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /mdbook-skill-tree/js/skill-tree.js: -------------------------------------------------------------------------------- 1 | // Loads the dot file found at `dot_path` as text and displays it. 2 | function loadSkillTree(dot_path) { 3 | var viz = new Viz(); 4 | fetch(dot_path) 5 | .then(response => response.text()) 6 | .then(text => { 7 | viz.renderSVGElement(text) 8 | .then(element => { document.body.appendChild(element); }) 9 | }); 10 | } 11 | 12 | function setSvgSizeAndViewBox(svgElem) { 13 | // Same as --content-max-width 14 | const defaultWidth = "750px"; 15 | const defaultHeight = "500px"; 16 | // Make the element have a fixed size, but use the width/height 17 | // to set the viewBox 18 | let width = svgElem.getAttribute("width").slice(0, -2); 19 | let height = svgElem.getAttribute("height").slice(0, -2); 20 | let viewBox = "0 0 " + width + " " + height; 21 | svgElem.setAttribute("width", defaultWidth); 22 | svgElem.setAttribute("height", defaultHeight); 23 | svgElem.setAttribute("viewBox", viewBox); 24 | svgElem.setAttribute("class", "skills-tree"); 25 | } 26 | 27 | function appendControls(parent, svgElem, pz) { 28 | const pzXform = pz.getTransform();; 29 | const initialZoom = {x: pzXform.x, y: pzXform.y, scale: pzXform.scale}; 30 | const controls = document.createElement("div"); 31 | controls.setAttribute("style", "padding-bottom: 4px"); 32 | 33 | const controlsText = document.createElement("i"); 34 | controlsText.setAttribute("style", "font-size: smaller"); 35 | controlsText.innerText = "Hold the alt/option key to zoom the skill tree; click and move to pan."; 36 | 37 | const resetButton = document.createElement("button"); 38 | resetButton.setAttribute("title", "Reset"); 39 | resetButton.setAttribute("aria-label", "Reset pan/zoom"); 40 | resetButton.innerHTML = '' 41 | resetButton.appendChild(document.createTextNode(" Reset Pan/Zoom")); 42 | resetButton.onclick = (e) => { 43 | pz.moveTo(initialZoom.x, initialZoom.y); 44 | pz.zoomAbs(initialZoom.x, initialZoom.y, initialZoom.scale); 45 | } 46 | controls.appendChild(controlsText); 47 | controls.appendChild(resetButton); 48 | controls.className = 'buttons'; 49 | parent.insertBefore(controls, svgElem); 50 | } 51 | 52 | function convertDivToSkillTree(divId, dotText) { 53 | new Viz().renderSVGElement(dotText.dot_text).then(svg_elem => { 54 | let parent = document.getElementById(divId); 55 | parent.appendChild(svg_elem); 56 | setSvgSizeAndViewBox(svg_elem); 57 | let element = svg_elem.children[0]; 58 | let pz = panzoom(element, { 59 | bounds: true, 60 | boundsPadding: 0.1, 61 | beforeWheel: function(e) { 62 | // allow wheel-zoom only if altKey is down. Otherwise - ignore 63 | var shouldIgnore = !e.altKey; 64 | return shouldIgnore; 65 | } 66 | }); 67 | appendControls(parent, svg_elem, pz) 68 | }) 69 | } 70 | 71 | for (let obj of SKILL_TREES) { 72 | convertDivToSkillTree(obj.id, obj.value); 73 | } 74 | -------------------------------------------------------------------------------- /mdbook-skill-tree/js/viz.js: -------------------------------------------------------------------------------- 1 | /* 2 | Viz.js 2.1.2 (Graphviz 2.40.1, Expat 2.2.5, Emscripten 1.37.36) 3 | Copyright (c) 2014-2018 Michael Daines 4 | Licensed under MIT license 5 | 6 | This distribution contains other software in object code form: 7 | 8 | Graphviz 9 | Licensed under Eclipse Public License - v 1.0 10 | http://www.graphviz.org 11 | 12 | Expat 13 | Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper 14 | Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers. 15 | Licensed under MIT license 16 | http://www.libexpat.org 17 | 18 | zlib 19 | Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler 20 | http://www.zlib.net/zlib_license.html 21 | */ 22 | (function (global, factory) { 23 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 24 | typeof define === 'function' && define.amd ? define(factory) : 25 | (global.Viz = factory()); 26 | }(this, (function () { 'use strict'; 27 | 28 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 29 | return typeof obj; 30 | } : function (obj) { 31 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 32 | }; 33 | 34 | var classCallCheck = function (instance, Constructor) { 35 | if (!(instance instanceof Constructor)) { 36 | throw new TypeError("Cannot call a class as a function"); 37 | } 38 | }; 39 | 40 | var createClass = function () { 41 | function defineProperties(target, props) { 42 | for (var i = 0; i < props.length; i++) { 43 | var descriptor = props[i]; 44 | descriptor.enumerable = descriptor.enumerable || false; 45 | descriptor.configurable = true; 46 | if ("value" in descriptor) descriptor.writable = true; 47 | Object.defineProperty(target, descriptor.key, descriptor); 48 | } 49 | } 50 | 51 | return function (Constructor, protoProps, staticProps) { 52 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 53 | if (staticProps) defineProperties(Constructor, staticProps); 54 | return Constructor; 55 | }; 56 | }(); 57 | 58 | var _extends = Object.assign || function (target) { 59 | for (var i = 1; i < arguments.length; i++) { 60 | var source = arguments[i]; 61 | 62 | for (var key in source) { 63 | if (Object.prototype.hasOwnProperty.call(source, key)) { 64 | target[key] = source[key]; 65 | } 66 | } 67 | } 68 | 69 | return target; 70 | }; 71 | 72 | var WorkerWrapper = function () { 73 | function WorkerWrapper(worker) { 74 | var _this = this; 75 | 76 | classCallCheck(this, WorkerWrapper); 77 | 78 | this.worker = worker; 79 | this.listeners = []; 80 | this.nextId = 0; 81 | 82 | this.worker.addEventListener('message', function (event) { 83 | var id = event.data.id; 84 | var error = event.data.error; 85 | var result = event.data.result; 86 | 87 | _this.listeners[id](error, result); 88 | delete _this.listeners[id]; 89 | }); 90 | } 91 | 92 | createClass(WorkerWrapper, [{ 93 | key: 'render', 94 | value: function render(src, options) { 95 | var _this2 = this; 96 | 97 | return new Promise(function (resolve, reject) { 98 | var id = _this2.nextId++; 99 | 100 | _this2.listeners[id] = function (error, result) { 101 | if (error) { 102 | reject(new Error(error.message, error.fileName, error.lineNumber)); 103 | return; 104 | } 105 | resolve(result); 106 | }; 107 | 108 | _this2.worker.postMessage({ id: id, src: src, options: options }); 109 | }); 110 | } 111 | }]); 112 | return WorkerWrapper; 113 | }(); 114 | 115 | var ModuleWrapper = function ModuleWrapper(module, render) { 116 | classCallCheck(this, ModuleWrapper); 117 | 118 | var instance = module(); 119 | this.render = function (src, options) { 120 | return new Promise(function (resolve, reject) { 121 | try { 122 | resolve(render(instance, src, options)); 123 | } catch (error) { 124 | reject(error); 125 | } 126 | }); 127 | }; 128 | }; 129 | 130 | // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding 131 | 132 | 133 | function b64EncodeUnicode(str) { 134 | return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { 135 | return String.fromCharCode('0x' + p1); 136 | })); 137 | } 138 | 139 | function defaultScale() { 140 | if ('devicePixelRatio' in window && window.devicePixelRatio > 1) { 141 | return window.devicePixelRatio; 142 | } else { 143 | return 1; 144 | } 145 | } 146 | 147 | function svgXmlToImageElement(svgXml) { 148 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 149 | _ref$scale = _ref.scale, 150 | scale = _ref$scale === undefined ? defaultScale() : _ref$scale, 151 | _ref$mimeType = _ref.mimeType, 152 | mimeType = _ref$mimeType === undefined ? "image/png" : _ref$mimeType, 153 | _ref$quality = _ref.quality, 154 | quality = _ref$quality === undefined ? 1 : _ref$quality; 155 | 156 | return new Promise(function (resolve, reject) { 157 | var svgImage = new Image(); 158 | 159 | svgImage.onload = function () { 160 | var canvas = document.createElement('canvas'); 161 | canvas.width = svgImage.width * scale; 162 | canvas.height = svgImage.height * scale; 163 | 164 | var context = canvas.getContext("2d"); 165 | context.drawImage(svgImage, 0, 0, canvas.width, canvas.height); 166 | 167 | canvas.toBlob(function (blob) { 168 | var image = new Image(); 169 | image.src = URL.createObjectURL(blob); 170 | image.width = svgImage.width; 171 | image.height = svgImage.height; 172 | 173 | resolve(image); 174 | }, mimeType, quality); 175 | }; 176 | 177 | svgImage.onerror = function (e) { 178 | var error; 179 | 180 | if ('error' in e) { 181 | error = e.error; 182 | } else { 183 | error = new Error('Error loading SVG'); 184 | } 185 | 186 | reject(error); 187 | }; 188 | 189 | svgImage.src = 'data:image/svg+xml;base64,' + b64EncodeUnicode(svgXml); 190 | }); 191 | } 192 | 193 | function svgXmlToImageElementFabric(svgXml) { 194 | var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 195 | _ref2$scale = _ref2.scale, 196 | scale = _ref2$scale === undefined ? defaultScale() : _ref2$scale, 197 | _ref2$mimeType = _ref2.mimeType, 198 | mimeType = _ref2$mimeType === undefined ? 'image/png' : _ref2$mimeType, 199 | _ref2$quality = _ref2.quality, 200 | quality = _ref2$quality === undefined ? 1 : _ref2$quality; 201 | 202 | var multiplier = scale; 203 | 204 | var format = void 0; 205 | if (mimeType == 'image/jpeg') { 206 | format = 'jpeg'; 207 | } else if (mimeType == 'image/png') { 208 | format = 'png'; 209 | } 210 | 211 | return new Promise(function (resolve, reject) { 212 | fabric.loadSVGFromString(svgXml, function (objects, options) { 213 | // If there's something wrong with the SVG, Fabric may return an empty array of objects. Graphviz appears to give us at least one element back even given an empty graph, so we will assume an error in this case. 214 | if (objects.length == 0) { 215 | reject(new Error('Error loading SVG with Fabric')); 216 | } 217 | 218 | var element = document.createElement("canvas"); 219 | element.width = options.width; 220 | element.height = options.height; 221 | 222 | var canvas = new fabric.Canvas(element, { enableRetinaScaling: false }); 223 | var obj = fabric.util.groupSVGElements(objects, options); 224 | canvas.add(obj).renderAll(); 225 | 226 | var image = new Image(); 227 | image.src = canvas.toDataURL({ format: format, multiplier: multiplier, quality: quality }); 228 | image.width = options.width; 229 | image.height = options.height; 230 | 231 | resolve(image); 232 | }); 233 | }); 234 | } 235 | 236 | var Viz = function () { 237 | function Viz() { 238 | var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 239 | workerURL = _ref3.workerURL, 240 | worker = _ref3.worker, 241 | Module = _ref3.Module, 242 | render = _ref3.render; 243 | 244 | classCallCheck(this, Viz); 245 | 246 | if (typeof workerURL !== 'undefined') { 247 | this.wrapper = new WorkerWrapper(new Worker(workerURL)); 248 | } else if (typeof worker !== 'undefined') { 249 | this.wrapper = new WorkerWrapper(worker); 250 | } else if (typeof Module !== 'undefined' && typeof render !== 'undefined') { 251 | this.wrapper = new ModuleWrapper(Module, render); 252 | } else if (typeof Viz.Module !== 'undefined' && typeof Viz.render !== 'undefined') { 253 | this.wrapper = new ModuleWrapper(Viz.Module, Viz.render); 254 | } else { 255 | throw new Error('Must specify workerURL or worker option, Module and render options, or include one of full.render.js or lite.render.js after viz.js.'); 256 | } 257 | } 258 | 259 | createClass(Viz, [{ 260 | key: 'renderString', 261 | value: function renderString(src) { 262 | var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 263 | _ref4$format = _ref4.format, 264 | format = _ref4$format === undefined ? 'svg' : _ref4$format, 265 | _ref4$engine = _ref4.engine, 266 | engine = _ref4$engine === undefined ? 'dot' : _ref4$engine, 267 | _ref4$files = _ref4.files, 268 | files = _ref4$files === undefined ? [] : _ref4$files, 269 | _ref4$images = _ref4.images, 270 | images = _ref4$images === undefined ? [] : _ref4$images, 271 | _ref4$yInvert = _ref4.yInvert, 272 | yInvert = _ref4$yInvert === undefined ? false : _ref4$yInvert, 273 | _ref4$nop = _ref4.nop, 274 | nop = _ref4$nop === undefined ? 0 : _ref4$nop; 275 | 276 | for (var i = 0; i < images.length; i++) { 277 | files.push({ 278 | path: images[i].path, 279 | data: '\n\n' 280 | }); 281 | } 282 | 283 | return this.wrapper.render(src, { format: format, engine: engine, files: files, images: images, yInvert: yInvert, nop: nop }); 284 | } 285 | }, { 286 | key: 'renderSVGElement', 287 | value: function renderSVGElement(src) { 288 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 289 | 290 | return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) { 291 | var parser = new DOMParser(); 292 | return parser.parseFromString(str, 'image/svg+xml').documentElement; 293 | }); 294 | } 295 | }, { 296 | key: 'renderImageElement', 297 | value: function renderImageElement(src) { 298 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 299 | var scale = options.scale, 300 | mimeType = options.mimeType, 301 | quality = options.quality; 302 | 303 | 304 | return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) { 305 | if ((typeof fabric === 'undefined' ? 'undefined' : _typeof(fabric)) === "object" && fabric.loadSVGFromString) { 306 | return svgXmlToImageElementFabric(str, { scale: scale, mimeType: mimeType, quality: quality }); 307 | } else { 308 | return svgXmlToImageElement(str, { scale: scale, mimeType: mimeType, quality: quality }); 309 | } 310 | }); 311 | } 312 | }, { 313 | key: 'renderJSONObject', 314 | value: function renderJSONObject(src) { 315 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 316 | var format = options.format; 317 | 318 | 319 | if (format !== 'json' || format !== 'json0') { 320 | format = 'json'; 321 | } 322 | 323 | return this.renderString(src, _extends({}, options, { format: format })).then(function (str) { 324 | return JSON.parse(str); 325 | }); 326 | } 327 | }]); 328 | return Viz; 329 | }(); 330 | 331 | return Viz; 332 | 333 | }))); 334 | -------------------------------------------------------------------------------- /mdbook-skill-tree/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use clap::{crate_version, App, Arg, ArgMatches, SubCommand}; 3 | use fehler::throws; 4 | use mdbook::errors::Error; 5 | use mdbook::preprocess::{CmdPreprocessor, Preprocessor}; 6 | use std::{ 7 | fs::{self, File}, 8 | io::{self, Write}, 9 | path::{Path, PathBuf}, 10 | process, 11 | }; 12 | use toml_edit::{value, Array, Document, Item, Table, Value}; 13 | 14 | mod preprocessor; 15 | use preprocessor::SkillTreePreprocessor; 16 | 17 | struct AdditionalFile { 18 | name: &'static str, 19 | bytes: &'static [u8], 20 | ty: &'static str, 21 | } 22 | 23 | // NB: Ordering matters here! 24 | const ADDITIONAL_FILES: &[AdditionalFile] = &[ 25 | AdditionalFile { 26 | name: "skill-tree.css", 27 | bytes: include_bytes!("../js/skill-tree.css"), 28 | ty: "css", 29 | }, 30 | AdditionalFile { 31 | name: "viz.js", 32 | bytes: include_bytes!("../js/viz.js"), 33 | ty: "js", 34 | }, 35 | AdditionalFile { 36 | name: "full.render.js", 37 | bytes: include_bytes!("../js/full.render.js"), 38 | ty: "js", 39 | }, 40 | AdditionalFile { 41 | name: "panzoom.min.js", 42 | bytes: include_bytes!("../js/panzoom.min.js"), 43 | ty: "js", 44 | }, 45 | AdditionalFile { 46 | name: "skill-tree.js", 47 | bytes: include_bytes!("../js/skill-tree.js"), 48 | ty: "js", 49 | }, 50 | ]; 51 | 52 | pub fn make_app() -> App<'static, 'static> { 53 | App::new("mdbook-skill-tree") 54 | .version(crate_version!()) 55 | .about("mdbook preprocessor to add skill-tree support") 56 | .subcommand( 57 | SubCommand::with_name("supports") 58 | .arg(Arg::with_name("renderer").required(true)) 59 | .about("Check whether a renderer is supported by this preprocessor"), 60 | ) 61 | .subcommand( 62 | SubCommand::with_name("install") 63 | .arg( 64 | Arg::with_name("dir") 65 | .default_value(".") 66 | .help("Root directory for the book,\nshould contain the configuration file (`book.toml`)") 67 | ) 68 | .about("]Install the required assset files and include it in the config"), 69 | ) 70 | } 71 | 72 | #[throws(anyhow::Error)] 73 | fn main() { 74 | env_logger::from_env(env_logger::Env::default().default_filter_or("info")).init(); 75 | 76 | let matches = make_app().get_matches(); 77 | 78 | if let Some(sub_args) = matches.subcommand_matches("supports") { 79 | handle_supports(sub_args); 80 | } else if let Some(sub_args) = matches.subcommand_matches("install") { 81 | handle_install(sub_args)?; 82 | } else { 83 | if let Err(e) = handle_preprocessing() { 84 | eprintln!("{}", e); 85 | process::exit(1); 86 | } 87 | } 88 | } 89 | 90 | fn handle_preprocessing() -> Result<(), Error> { 91 | let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; 92 | 93 | if ctx.mdbook_version != mdbook::MDBOOK_VERSION { 94 | eprintln!( 95 | "Warning: The mdbook-skill-tree preprocessor was built against version \ 96 | {} of mdbook, but we're being called from version {}", 97 | mdbook::MDBOOK_VERSION, 98 | ctx.mdbook_version 99 | ); 100 | } 101 | 102 | let processed_book = SkillTreePreprocessor.run(&ctx, book)?; 103 | serde_json::to_writer(io::stdout(), &processed_book)?; 104 | 105 | Ok(()) 106 | } 107 | 108 | fn handle_supports(sub_args: &ArgMatches) -> ! { 109 | let renderer = sub_args.value_of("renderer").expect("Required argument"); 110 | let supported = SkillTreePreprocessor.supports_renderer(&renderer); 111 | 112 | // Signal whether the renderer is supported by exiting with 1 or 0. 113 | if supported { 114 | process::exit(0); 115 | } else { 116 | process::exit(1); 117 | } 118 | } 119 | 120 | #[throws(anyhow::Error)] 121 | fn handle_install(sub_args: &ArgMatches) { 122 | let dir = sub_args.value_of("dir").expect("Required argument"); 123 | let proj_dir = PathBuf::from(dir); 124 | let config = proj_dir.join("book.toml"); 125 | 126 | if !config.exists() { 127 | log::error!("Configuration file '{}' missing", config.display()); 128 | process::exit(1); 129 | } 130 | 131 | log::info!("Reading configuration file {}", config.display()); 132 | let toml = fs::read_to_string(&config).expect("can't read configuration file"); 133 | let mut doc = toml 134 | .parse::() 135 | .expect("configuration is not valid TOML"); 136 | 137 | let has_pre = has_preprocessor(&mut doc); 138 | if !has_pre { 139 | log::info!("Adding preprocessor configuration"); 140 | add_preprocessor(&mut doc); 141 | } 142 | 143 | let added_additional_files = add_additional_files(&mut doc); 144 | if !has_pre || added_additional_files { 145 | log::info!("Saving changed configuration to {}", config.display()); 146 | let toml = doc.to_string_in_original_order(); 147 | let mut file = File::create(config).expect("can't open configuration file for writing."); 148 | file.write_all(toml.as_bytes()) 149 | .expect("can't write configuration"); 150 | } 151 | 152 | let mut printed = false; 153 | 154 | // Copy into it the content from viz-js folder 155 | for file in ADDITIONAL_FILES { 156 | let output_path = proj_dir.join(file.name); 157 | if output_path.exists() { 158 | log::debug!( 159 | "'{}' already exists (Path: {}). Skipping.", 160 | file.name, 161 | output_path.display() 162 | ); 163 | continue; 164 | } 165 | if !printed { 166 | printed = true; 167 | log::info!( 168 | "Writing additional files to project directory at {}", 169 | proj_dir.display() 170 | ); 171 | } 172 | log::debug!( 173 | "Writing content for '{}' into {}", 174 | file.name, 175 | output_path.display() 176 | ); 177 | write_static_file(&output_path, file.bytes) 178 | .with_context(|| format!("creating static file `{}`", output_path.display()))?; 179 | } 180 | 181 | log::info!("Files & configuration for mdbook-skill-tree are installed. You can start using it in your book."); 182 | } 183 | 184 | #[throws(anyhow::Error)] 185 | fn write_static_file(output_path: &Path, file_text: &[u8]) { 186 | let mut file = File::create(output_path)?; 187 | file.write_all(&file_text)?; 188 | } 189 | 190 | fn add_additional_files(doc: &mut Document) -> bool { 191 | let mut changed = false; 192 | let mut printed = true; 193 | 194 | for file in ADDITIONAL_FILES { 195 | let additional = additional(doc, file.ty); 196 | if has_file(&additional, file.name) { 197 | log::debug!( 198 | "'{}' already in 'additional-{}'. Skipping", 199 | file.name, 200 | file.ty 201 | ) 202 | } else { 203 | if !printed { 204 | printed = true; 205 | log::info!("Adding additional files to configuration"); 206 | } 207 | log::debug!("Adding '{}' to 'additional-{}'", file.name, file.ty); 208 | insert_additional(doc, file.ty, file.name); 209 | changed = true; 210 | } 211 | } 212 | 213 | changed 214 | } 215 | 216 | fn additional<'a>(doc: &'a mut Document, additional_type: &str) -> Option<&'a mut Array> { 217 | let doc = doc.as_table_mut(); 218 | 219 | let item = doc.entry("output"); 220 | let item = item 221 | .or_insert(empty_implicit_table()) 222 | .as_table_mut()? 223 | .entry("html"); 224 | let item = item 225 | .or_insert(empty_implicit_table()) 226 | .as_table_mut()? 227 | .entry(&format!("additional-{}", additional_type)); 228 | item.as_array_mut() 229 | } 230 | 231 | fn has_preprocessor(doc: &mut Document) -> bool { 232 | matches!(doc["preprocessor"]["skill-tree"], Item::Table(_)) 233 | } 234 | 235 | fn empty_implicit_table() -> Item { 236 | let mut empty_table = Table::default(); 237 | empty_table.set_implicit(true); 238 | Item::Table(empty_table) 239 | } 240 | 241 | fn add_preprocessor(doc: &mut Document) { 242 | let doc = doc.as_table_mut(); 243 | 244 | let item = doc.entry("preprocessor").or_insert(empty_implicit_table()); 245 | let item = item 246 | .as_table_mut() 247 | .unwrap() 248 | .entry("skill-tree") 249 | .or_insert(empty_implicit_table()); 250 | item["command"] = value("mdbook-skill-tree"); 251 | } 252 | 253 | fn has_file(elem: &Option<&mut Array>, file: &str) -> bool { 254 | match elem { 255 | Some(elem) => elem.iter().any(|elem| match elem.as_str() { 256 | None => true, 257 | Some(s) => s.ends_with(file), 258 | }), 259 | None => false, 260 | } 261 | } 262 | 263 | fn insert_additional(doc: &mut Document, additional_type: &str, file: &str) { 264 | let doc = doc.as_table_mut(); 265 | 266 | let empty_table = Item::Table(Table::default()); 267 | let empty_array = Item::Value(Value::Array(Array::default())); 268 | let item = doc.entry("output").or_insert(empty_table.clone()); 269 | let item = item 270 | .as_table_mut() 271 | .unwrap() 272 | .entry("html") 273 | .or_insert(empty_table.clone()); 274 | let array = item 275 | .as_table_mut() 276 | .unwrap() 277 | .entry(&format!("additional-{}", additional_type)) 278 | .or_insert(empty_array); 279 | array 280 | .as_value_mut() 281 | .unwrap() 282 | .as_array_mut() 283 | .unwrap() 284 | .push(file); 285 | } 286 | -------------------------------------------------------------------------------- /mdbook-skill-tree/src/preprocessor.rs: -------------------------------------------------------------------------------- 1 | use mdbook::book::{Book, BookItem}; 2 | use mdbook::errors::{Error, Result}; 3 | use mdbook::preprocess::{Preprocessor, PreprocessorContext}; 4 | use pulldown_cmark::{CodeBlockKind::*, Event, Options, Parser, Tag}; 5 | use pulldown_cmark_to_cmark::cmark; 6 | use serde_json::json; 7 | use skill_tree::SkillTree; 8 | use std::fmt::Write; 9 | 10 | #[derive(Default)] 11 | pub struct SkillTreePreprocessor; 12 | 13 | impl Preprocessor for SkillTreePreprocessor { 14 | fn name(&self) -> &str { 15 | "skill-tree" 16 | } 17 | 18 | fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result { 19 | let mut counter = 0; 20 | let mut res = None; 21 | book.for_each_mut(|item: &mut BookItem| { 22 | if let Some(Err(_)) = res { 23 | return; 24 | } 25 | 26 | if let BookItem::Chapter(chapter) = item { 27 | res = Some(add_skill_tree(&chapter.content, &mut counter).map(|md| { 28 | chapter.content = md; 29 | })); 30 | } 31 | }); 32 | 33 | res.unwrap_or(Ok(())).map(|_| book) 34 | } 35 | 36 | fn supports_renderer(&self, renderer: &str) -> bool { 37 | renderer == "html" 38 | } 39 | } 40 | 41 | fn add_skill_tree(content: &str, counter: &mut usize) -> Result { 42 | let mut buf = String::with_capacity(content.len()); 43 | let mut skill_tree_content = String::new(); 44 | let mut in_skill_tree_block = false; 45 | 46 | let mut opts = Options::empty(); 47 | opts.insert(Options::ENABLE_TABLES); 48 | opts.insert(Options::ENABLE_FOOTNOTES); 49 | opts.insert(Options::ENABLE_STRIKETHROUGH); 50 | opts.insert(Options::ENABLE_TASKLISTS); 51 | 52 | let events = Parser::new_ext(content, opts).map(|e| { 53 | if let Event::Start(Tag::CodeBlock(Fenced(code))) = e.clone() { 54 | if &*code == "skill-tree" { 55 | in_skill_tree_block = true; 56 | skill_tree_content.clear(); 57 | return None; 58 | } else { 59 | return Some(e); 60 | } 61 | } 62 | 63 | if !in_skill_tree_block { 64 | return Some(e); 65 | } 66 | 67 | match e { 68 | Event::End(Tag::CodeBlock(Fenced(code))) => { 69 | assert_eq!( 70 | "skill-tree", &*code, 71 | "After an opening sjill-tree code block we expect it to close again" 72 | ); 73 | in_skill_tree_block = false; 74 | 75 | let graphviz_text_or_err = SkillTree::parse(&skill_tree_content) 76 | .and_then(|skill_tree| skill_tree.to_graphviz()); 77 | let js_value = match graphviz_text_or_err { 78 | Ok(text) => json!({ 79 | "dot_text": text, 80 | "error": "", 81 | }), 82 | 83 | // FIXME -- we should serialize this into something that displays the error 84 | // when rendered 85 | Err(e) => panic!("encountered error {} parsing {:?}", e, skill_tree_content), 86 | }; 87 | 88 | // Get a fresh id for this block. 89 | let id = *counter; 90 | *counter += 1; 91 | 92 | // Generate a "div" where the rendered code will go with 93 | // a unique `id`. 94 | let mut html_code = String::new(); 95 | write!(&mut html_code, "
", id).unwrap(); 96 | write!(&mut html_code, "
\n\n").unwrap(); 97 | 98 | // Generate a script tag to insert the rendered skill-tree 99 | // content. It is given a string argument with the graphviz 100 | // output we can pass to viz-js. 101 | write!( 102 | &mut html_code, 103 | r#""#, 107 | id, js_value 108 | ) 109 | .unwrap(); 110 | return Some(Event::Html(html_code.into())); 111 | } 112 | Event::Text(code) => { 113 | skill_tree_content.push_str(&code); 114 | } 115 | _ => return Some(e), 116 | } 117 | 118 | None 119 | }); 120 | let events = events.filter_map(|e| e); 121 | cmark(events, &mut buf, None) 122 | .map(|_| buf) 123 | .map_err(|err| Error::msg(format!("Markdown serialization failed: {}", err))) 124 | } 125 | 126 | #[cfg(test)] 127 | mod test; 128 | -------------------------------------------------------------------------------- /mdbook-skill-tree/src/preprocessor/test.rs: -------------------------------------------------------------------------------- 1 | use super::add_skill_tree; 2 | 3 | #[test] 4 | fn adds_skill_tree() { 5 | let content = r#"# Chapter 6 | 7 | ```skill-tree 8 | [[group]] 9 | name = "test" 10 | label = "test" 11 | items = [ ] 12 | ``` 13 | 14 | Text 15 | "#; 16 | 17 | let output = add_skill_tree(content, &mut 0).unwrap(); 18 | println!("output:\n{}", output); 19 | assert!(output.contains(r#"
"#)); 20 | } 21 | 22 | #[test] 23 | fn leaves_tables_untouched() { 24 | // Regression test. 25 | // Previously we forgot to enable the same markdwon extensions as mdbook itself. 26 | 27 | let content = r#"# Heading 28 | 29 | | Head 1 | Head 2 | 30 | |--------|--------| 31 | | Row 1 | Row 2 | 32 | "#; 33 | 34 | // Markdown roundtripping removes some insignificant whitespace 35 | let expected = r#"# Heading 36 | 37 | |Head 1|Head 2| 38 | |------|------| 39 | |Row 1|Row 2|"#; 40 | 41 | assert_eq!(expected, add_skill_tree(content, &mut 0).unwrap()); 42 | } 43 | 44 | #[test] 45 | fn leaves_html_untouched() { 46 | // Regression test. 47 | // Don't remove important newlines for syntax nested inside HTML 48 | 49 | let content = r#"# Heading 50 | 51 | 52 | 53 | *foo* 54 | 55 | 56 | "#; 57 | 58 | // Markdown roundtripping removes some insignificant whitespace 59 | let expected = r#"# Heading 60 | 61 | 62 | 63 | *foo* 64 | 65 | 66 | "#; 67 | 68 | assert_eq!(expected, add_skill_tree(content, &mut 0).unwrap()); 69 | } 70 | -------------------------------------------------------------------------------- /src/graphviz.rs: -------------------------------------------------------------------------------- 1 | use crate::tree::{Graphviz, Group, ItemExt, SkillTree, Status}; 2 | use fehler::throws; 3 | use std::io::Write; 4 | 5 | impl SkillTree { 6 | /// Writes graphviz representing this skill-tree to the given output. 7 | #[throws(anyhow::Error)] 8 | pub fn write_graphviz(&self, output: &mut dyn Write) { 9 | write_graphviz(self, output)? 10 | } 11 | 12 | /// Generates a string containing graphviz content for this skill-tree. 13 | #[throws(anyhow::Error)] 14 | pub fn to_graphviz(&self) -> String { 15 | let mut output = Vec::new(); 16 | write_graphviz(self, &mut output)?; 17 | String::from_utf8(output)? 18 | } 19 | } 20 | 21 | #[throws(anyhow::Error)] 22 | fn write_graphviz(tree: &SkillTree, output: &mut dyn Write) { 23 | let rankdir = match &tree.graphviz { 24 | Some(Graphviz { 25 | rankdir: Some(rankdir), 26 | .. 27 | }) => &rankdir[..], 28 | _ => "LR", 29 | }; 30 | writeln!(output, r#"digraph g {{"#)?; 31 | writeln!( 32 | output, 33 | r#"graph [ rankdir = "{rankdir}" ];"#, 34 | rankdir = rankdir 35 | )?; 36 | writeln!(output, r#"node [ fontsize="16", shape = "ellipse" ];"#)?; 37 | writeln!(output, r#"edge [ ];"#)?; 38 | 39 | if let Some(clusters) = &tree.cluster { 40 | for cluster in clusters { 41 | let cluster_name = format!("cluster_{}", cluster.name); 42 | writeln!( 43 | output, 44 | r#"subgraph {cluster_name} {{"#, 45 | cluster_name = cluster_name 46 | )?; 47 | writeln!(output, r#" label="{}";"#, cluster.label)?; 48 | write_cluster(tree, output, Some(&cluster.name))?; 49 | writeln!(output, r#"}}"#)?; 50 | } 51 | } 52 | write_cluster(tree, output, None)?; 53 | 54 | for group in tree.groups() { 55 | if let Some(requires) = &group.requires { 56 | for requirement in requires { 57 | writeln!(output, r#""{}" -> "{}";"#, &group.name, requirement)?; 58 | } 59 | } 60 | } 61 | 62 | writeln!(output, r#"}}"#)?; 63 | } 64 | 65 | #[throws(anyhow::Error)] 66 | fn write_cluster(tree: &SkillTree, output: &mut dyn Write, cluster: Option<&String>) { 67 | for group in tree.groups() { 68 | // If we are doing a cluster, the group must be in it; 69 | // otherwise, the group must not be in any cluster. 70 | match (&group.cluster, cluster) { 71 | (None, None) => {} 72 | (Some(c1), Some(c2)) if c1 == c2 => {} 73 | _ => continue, 74 | } 75 | writeln!(output, r#""{}" ["#, group.name)?; 76 | write_group_label(tree, group, output)?; 77 | writeln!(output, r#" shape = "none""#)?; 78 | writeln!(output, r#" margin = 0"#)?; 79 | writeln!(output, r#"]"#)?; 80 | } 81 | } 82 | 83 | const WATCH_EMOJI: &str = "⌚"; 84 | const HAMMER_WRENCH_EMOJI: &str = "🛠️"; 85 | const CHECKED_BOX_EMOJI: &str = "☑️"; 86 | const RAISED_HAND_EMOJI: &str = "🙋"; 87 | 88 | fn escape(s: &str) -> String { 89 | htmlescape::encode_minimal(s).replace('\n', "
") 90 | } 91 | 92 | #[throws(anyhow::Error)] 93 | fn write_group_label(tree: &SkillTree, group: &Group, output: &mut dyn Write) { 94 | writeln!(output, r#" label = <"#)?; 95 | 96 | let label = group.label.as_ref().unwrap_or(&group.name); 97 | let label = escape(label); 98 | let group_href = attribute_str("href", &group.href, ""); 99 | let header_color = group 100 | .header_color 101 | .as_ref() 102 | .map(String::as_str) 103 | .unwrap_or("darkgoldenrod"); 104 | let description_color = group 105 | .description_color 106 | .as_ref() 107 | .map(String::as_str) 108 | .unwrap_or("darkgoldenrod1"); 109 | 110 | // We have one column for each thing specified by user, plus the label. 111 | let columns = tree.columns().len() + 1; 112 | 113 | writeln!( 114 | output, 115 | r#" "#, 116 | group_href = group_href, 117 | label = label, 118 | header_color = header_color, 119 | columns = columns, 120 | )?; 121 | 122 | for label in group.description.iter().flatten() { 123 | writeln!( 124 | output, 125 | r#" "#, 126 | group_href = group_href, 127 | label = label, 128 | description_color = description_color, 129 | columns = columns, 130 | )?; 131 | } 132 | 133 | for item in &group.items { 134 | let item_status = Status::Unassigned; // XXX 135 | let (_emoji, _fontcolor, mut start_tag, mut end_tag) = match item_status { 136 | Status::Blocked => ( 137 | WATCH_EMOJI, 138 | None, 139 | "", 140 | "", 141 | ), 142 | Status::Unassigned => (RAISED_HAND_EMOJI, Some("red"), "", ""), 143 | Status::Assigned => (HAMMER_WRENCH_EMOJI, None, "", ""), 144 | Status::Complete => (CHECKED_BOX_EMOJI, None, "", ""), 145 | }; 146 | 147 | let bgcolor = attribute_str("bgcolor", &Some("cornsilk"), ""); 148 | let href = attribute_str("href", &item.href(), ""); 149 | if item.href().is_some() && start_tag == "" { 150 | start_tag = ""; 151 | end_tag = ""; 152 | } 153 | write!(output, " ")?; 154 | 155 | for column in tree.columns() { 156 | let item_value = item.column_value(tree, column); 157 | let emoji = tree.emoji(column, item_value); 158 | write!( 159 | output, 160 | "{emoji}", 161 | bgcolor = bgcolor, 162 | emoji = emoji 163 | )?; 164 | } 165 | 166 | write!( 167 | output, 168 | "{start_tag}{label}{end_tag}", 169 | bgcolor = bgcolor, 170 | href = href, 171 | label = item.label(), 172 | start_tag = start_tag, 173 | end_tag = end_tag, 174 | )?; 175 | 176 | writeln!(output, "")?; 177 | } 178 | 179 | writeln!(output, r#"
{label}
{label}
>"#)?; 180 | } 181 | 182 | fn attribute_str(label: &str, text: &Option>, suffix: &str) -> String { 183 | match text { 184 | None => format!(""), 185 | Some(t) => format!(" {}=\"{}{}\"", label, t.as_ref(), suffix), 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod graphviz; 2 | mod tree; 3 | pub use tree::*; 4 | mod test; 5 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use fehler::throws; 3 | use skill_tree::SkillTree; 4 | use std::fs::File; 5 | use std::path::PathBuf; 6 | use structopt::StructOpt; 7 | 8 | #[derive(StructOpt, Debug)] 9 | #[structopt(name = "skill-tree")] 10 | struct Opts { 11 | #[structopt(name = "skill_tree", parse(from_os_str))] 12 | skill_tree: PathBuf, 13 | 14 | #[structopt(name = "output_path", parse(from_os_str))] 15 | output_path: PathBuf, 16 | } 17 | 18 | #[throws(anyhow::Error)] 19 | fn main() { 20 | let opts: Opts = Opts::from_args(); 21 | 22 | // Load the skill tree 23 | let skill_tree = SkillTree::load(&opts.skill_tree) 24 | .with_context(|| format!("loading skill tree from `{}`", opts.skill_tree.display()))?; 25 | 26 | // Validate it for errors. 27 | skill_tree.validate()?; 28 | 29 | // Write out the dot file 30 | write_dot_file(&skill_tree, &opts)?; 31 | } 32 | 33 | #[throws(anyhow::Error)] 34 | fn write_dot_file(skill_tree: &SkillTree, opts: &Opts) { 35 | let dot_path = &opts.output_path; 36 | let mut dot_file = 37 | File::create(dot_path).with_context(|| format!("creating `{}`", dot_path.display()))?; 38 | skill_tree 39 | .write_graphviz(&mut dot_file) 40 | .with_context(|| format!("writing to `{}`", dot_path.display()))?; 41 | } 42 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use std::path::PathBuf; 4 | 5 | use crate::SkillTree; 6 | 7 | const BLESS: bool = false; 8 | 9 | fn run_test(file_name: &str) { 10 | let toml_file = PathBuf::from(format!("test-data/{}.toml", file_name)); 11 | let skill_tree = SkillTree::load(&toml_file).unwrap(); 12 | skill_tree.validate().unwrap(); 13 | let mut actual_output_buf = Vec::new(); 14 | skill_tree.write_graphviz(&mut actual_output_buf).unwrap(); 15 | let actual_output = String::from_utf8(actual_output_buf).unwrap(); 16 | 17 | let expected_file = PathBuf::from(format!("test-data/{}.gv", file_name)); 18 | let expected_output = std::fs::read_to_string(&expected_file).unwrap_or_default(); 19 | 20 | let expected_lines = expected_output.lines().chain(Some("EOF")); 21 | let actual_lines = actual_output.lines().chain(Some("EOF")); 22 | if let Some(first_diff) = expected_lines.zip(actual_lines).position(|(e, a)| e != a) { 23 | if BLESS || std::env::var("AVD_BLESS").is_ok() { 24 | std::fs::write(&expected_file, actual_output).unwrap(); 25 | eprintln!("blessing {}", expected_file.display()); 26 | } else { 27 | eprintln!( 28 | "{}", 29 | prettydiff::diff_lines(&expected_output, &actual_output) 30 | ); 31 | panic!("expected output differs on line {}", first_diff + 1); 32 | } 33 | } 34 | } 35 | 36 | #[test] 37 | fn avd_snippet() { 38 | run_test("avd_snippet"); 39 | } 40 | 41 | #[test] 42 | #[should_panic(expected = "the group `A` has a dependency on a group `B` that does not exist")] 43 | fn invalid_requires() { 44 | run_test("invalid_requires"); 45 | } 46 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use fehler::throws; 3 | use serde_derive::Deserialize; 4 | use std::{ 5 | collections::{HashMap, HashSet}, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | #[derive(Debug, Deserialize)] 10 | pub struct SkillTree { 11 | pub group: Option>, 12 | pub cluster: Option>, 13 | pub graphviz: Option, 14 | pub doc: Option, 15 | } 16 | 17 | #[derive(Debug, Deserialize)] 18 | pub struct Graphviz { 19 | pub rankdir: Option, 20 | } 21 | 22 | #[derive(Default, Debug, Deserialize)] 23 | pub struct Doc { 24 | pub columns: Option>, 25 | pub defaults: Option>, 26 | pub emoji: Option>, 27 | pub include: Option>, 28 | } 29 | 30 | pub type EmojiMap = HashMap; 31 | 32 | #[derive(Debug, Deserialize)] 33 | pub struct Cluster { 34 | pub name: String, 35 | pub label: String, 36 | pub color: Option, 37 | pub style: Option, 38 | } 39 | 40 | #[derive(Clone, Debug, Deserialize)] 41 | pub struct Group { 42 | pub name: String, 43 | pub cluster: Option, 44 | pub label: Option, 45 | pub requires: Option>, 46 | pub description: Option>, 47 | pub items: Vec, 48 | pub width: Option, 49 | pub status: Option, 50 | pub href: Option, 51 | pub header_color: Option, 52 | pub description_color: Option, 53 | } 54 | 55 | #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] 56 | pub struct GroupIndex(pub usize); 57 | 58 | pub type Item = HashMap; 59 | 60 | #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] 61 | pub struct ItemIndex(pub usize); 62 | 63 | #[derive(Copy, Clone, Debug, Deserialize)] 64 | pub enum Status { 65 | /// Can't work on it now 66 | Blocked, 67 | 68 | /// Would like to work on it, but need someone 69 | Unassigned, 70 | 71 | /// People are actively working on it 72 | Assigned, 73 | 74 | /// This is done! 75 | Complete, 76 | } 77 | 78 | impl SkillTree { 79 | pub fn load(path: &Path) -> anyhow::Result { 80 | let loaded = &mut HashSet::default(); 81 | loaded.insert(path.to_owned()); 82 | Self::load_included_path(path, loaded) 83 | } 84 | 85 | fn load_included_path(path: &Path, loaded: &mut HashSet) -> anyhow::Result { 86 | fn load(path: &Path, loaded: &mut HashSet) -> anyhow::Result { 87 | let skill_tree_text = std::fs::read_to_string(path)?; 88 | let mut tree = SkillTree::parse(&skill_tree_text)?; 89 | tree.import(path, loaded)?; 90 | Ok(tree) 91 | } 92 | 93 | load(path, loaded).with_context(|| format!("loading skill tree from `{}`", path.display())) 94 | } 95 | 96 | fn import(&mut self, root_path: &Path, loaded: &mut HashSet) -> anyhow::Result<()> { 97 | if let Some(doc) = &mut self.doc { 98 | if let Some(include) = &mut doc.include { 99 | let include = include.clone(); 100 | for include_path in include { 101 | if !loaded.insert(include_path.clone()) { 102 | continue; 103 | } 104 | 105 | let tree_path = root_path.parent().unwrap().join(&include_path); 106 | let mut toml: SkillTree = SkillTree::load_included_path(&tree_path, loaded)?; 107 | 108 | // merge columns, and any defaults/emojis associated with the new columns 109 | let self_doc = self.doc.get_or_insert(Doc::default()); 110 | let toml_doc = toml.doc.get_or_insert(Doc::default()); 111 | for column in toml_doc.columns.get_or_insert(vec![]).iter() { 112 | let columns = self_doc.columns.get_or_insert(vec![]); 113 | if !columns.contains(column) { 114 | columns.push(column.clone()); 115 | 116 | if let Some(value) = 117 | toml_doc.emoji.get_or_insert(HashMap::default()).get(column) 118 | { 119 | self_doc 120 | .emoji 121 | .get_or_insert(HashMap::default()) 122 | .insert(column.clone(), value.clone()); 123 | } 124 | 125 | if let Some(value) = toml_doc 126 | .defaults 127 | .get_or_insert(HashMap::default()) 128 | .get(column) 129 | { 130 | self_doc 131 | .defaults 132 | .get_or_insert(HashMap::default()) 133 | .insert(column.clone(), value.clone()); 134 | } 135 | } 136 | } 137 | 138 | self.group 139 | .get_or_insert(vec![]) 140 | .extend(toml.groups().cloned()); 141 | 142 | self.cluster 143 | .get_or_insert(vec![]) 144 | .extend(toml.cluster.into_iter().flatten()); 145 | } 146 | } 147 | } 148 | Ok(()) 149 | } 150 | 151 | #[throws(anyhow::Error)] 152 | pub fn parse(text: &str) -> SkillTree { 153 | toml::from_str(text)? 154 | } 155 | 156 | #[throws(anyhow::Error)] 157 | pub fn validate(&self) { 158 | // gather: valid requires entries 159 | 160 | for group in self.groups() { 161 | group.validate(self)?; 162 | } 163 | } 164 | 165 | pub fn groups(&self) -> impl Iterator { 166 | match self.group { 167 | Some(ref g) => g.iter(), 168 | None => [].iter(), 169 | } 170 | } 171 | 172 | pub fn group_named(&self, name: &str) -> Option<&Group> { 173 | self.groups().find(|g| g.name == name) 174 | } 175 | 176 | /// Returns the expected column titles for each item (excluding the label). 177 | pub fn columns(&self) -> &[String] { 178 | if let Some(doc) = &self.doc { 179 | if let Some(columns) = &doc.columns { 180 | return columns; 181 | } 182 | } 183 | 184 | &[] 185 | } 186 | 187 | /// Translates an "input" into an emoji, returning "input" if not found. 188 | pub fn emoji<'me>(&'me self, column: &str, input: &'me str) -> &'me str { 189 | if let Some(doc) = &self.doc { 190 | if let Some(emoji_maps) = &doc.emoji { 191 | if let Some(emoji_map) = emoji_maps.get(column) { 192 | if let Some(output) = emoji_map.get(input) { 193 | return output; 194 | } 195 | } 196 | } 197 | } 198 | input 199 | } 200 | } 201 | 202 | impl Group { 203 | #[throws(anyhow::Error)] 204 | pub fn validate(&self, tree: &SkillTree) { 205 | // check: that `name` is a valid graphviz identifier 206 | 207 | // check: each of the things in requires has the form 208 | // `identifier` or `identifier:port` and that all those 209 | // identifiers map to groups 210 | 211 | for group_name in self.requires.iter().flatten() { 212 | if tree.group_named(group_name).is_none() { 213 | anyhow::bail!( 214 | "the group `{}` has a dependency on a group `{}` that does not exist", 215 | self.name, 216 | group_name, 217 | ) 218 | } 219 | } 220 | 221 | for item in &self.items { 222 | item.validate()?; 223 | } 224 | } 225 | 226 | pub fn items(&self) -> impl Iterator { 227 | self.items.iter() 228 | } 229 | } 230 | 231 | pub trait ItemExt { 232 | fn href(&self) -> Option<&String>; 233 | fn label(&self) -> &String; 234 | fn column_value<'me>(&'me self, tree: &'me SkillTree, c: &str) -> &'me str; 235 | 236 | #[allow(redundant_semicolons)] // bug in "throws" 237 | #[throws(anyhow::Error)] 238 | fn validate(&self); 239 | } 240 | 241 | impl ItemExt for Item { 242 | fn href(&self) -> Option<&String> { 243 | self.get("href") 244 | } 245 | 246 | fn label(&self) -> &String { 247 | self.get("label").unwrap() 248 | } 249 | 250 | fn column_value<'me>(&'me self, tree: &'me SkillTree, c: &str) -> &'me str { 251 | if let Some(v) = self.get(c) { 252 | return v; 253 | } 254 | 255 | if let Some(doc) = &tree.doc { 256 | if let Some(defaults) = &doc.defaults { 257 | if let Some(default_value) = defaults.get(c) { 258 | return default_value; 259 | } 260 | } 261 | } 262 | 263 | "" 264 | } 265 | 266 | #[throws(anyhow::Error)] 267 | fn validate(&self) { 268 | // check: each of the things in requires has the form 269 | // `identifier` or `identifier:port` and that all those 270 | // identifiers map to groups 271 | 272 | // check: only contains known keys 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /test-data/avd_snippet.gv: -------------------------------------------------------------------------------- 1 | digraph g { 2 | graph [ rankdir = "TD" ]; 3 | node [ fontsize="16", shape = "ellipse" ]; 4 | edge [ ]; 5 | "async-traits" [ 6 | label = < 7 | 8 | 9 | 10 | 11 |
Unergonomic async fns in traits
Write non-dyn-safe traits that can have fns that return futures
T-langType alias impl Trait
T-libsGeneric associated types
> 12 | shape = "none" 13 | margin = 0 14 | ] 15 | "async-fn-everywhere" [ 16 | label = < 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
Async fn everywhere
Write async fn anywhere you can write fn
Write async closures anywhere you can write sync closures
wg-asyncSupport for `dyn Trait` where `Trait` has async fn
wg-asyncAsync fn sugar in traits
wg-asyncAsync closure support
wg-asyncBoxable, recursive async fn
> 25 | shape = "none" 26 | margin = 0 27 | ] 28 | "async-traits" -> "async-fn-everywhere"; 29 | } 30 | -------------------------------------------------------------------------------- /test-data/avd_snippet.toml: -------------------------------------------------------------------------------- 1 | [graphviz] 2 | rankdir = "TD" 3 | 4 | [doc] 5 | columns = [ 6 | "team", 7 | ] 8 | 9 | [doc.defaults] 10 | team = "async" 11 | 12 | [doc.emoji.team] 13 | "lang" = "T-lang" 14 | "libs" = "T-libs" 15 | "async" = "wg-async" 16 | 17 | [[group]] 18 | name = "async-traits" 19 | label = "Unergonomic async fns in traits" 20 | description = [ 21 | "Write non-dyn-safe traits that can have fns that return futures", 22 | ] 23 | items = [ 24 | { label = "Type alias impl Trait", team = "lang" }, 25 | { label = "Generic associated types", team = "libs" }, 26 | ] 27 | 28 | [[group]] 29 | name = "async-fn-everywhere" 30 | label = "Async fn everywhere" 31 | description = [ 32 | "Write async fn anywhere you can write fn", 33 | "Write async closures anywhere you can write sync closures", 34 | ] 35 | requires = [ 36 | "async-traits", 37 | ] 38 | items = [ 39 | { label = "Support for `dyn Trait` where `Trait` has async fn" }, 40 | { label = "Async fn sugar in traits" }, 41 | { label = "Async closure support" }, 42 | { label = "Boxable, recursive async fn" }, 43 | ] 44 | -------------------------------------------------------------------------------- /test-data/invalid_requires.toml: -------------------------------------------------------------------------------- 1 | [[group]] 2 | name = "A" 3 | label = "Unergonomic async fns in traits" 4 | requires = ["B"] 5 | items = [ 6 | { label = "Type alias impl Trait" }, 7 | { label = "Generic associated types" }, 8 | ] 9 | 10 | -------------------------------------------------------------------------------- /tree-data/example.toml: -------------------------------------------------------------------------------- 1 | [[group]] 2 | name = "legend" 3 | label = "Legend" 4 | header_color = "red" 5 | items = [ 6 | { label = "Unassigned", status = "Unassigned" }, 7 | { label = "Blocked", status = "Blocked" }, 8 | { label = "Assigned / In-progress", status = "Assigned" }, 9 | { label = "Complete", status = "Complete" } 10 | ] 11 | 12 | [[group]] 13 | name = "align-rustc-predicate" 14 | label = "Align rustc predicates with chalk predicates" 15 | items = [ 16 | { label = "isolate Binder into a Forall goal" }, 17 | { label = "introduce Implication" }, 18 | { label = "introduce Forall goals with types" }, 19 | ] 20 | 21 | [[group]] 22 | name = "recursive-solver" 23 | label = "Experiment with a recursive chalk solver" 24 | items = [ 25 | { label = "write-up the idea that Niko had" }, 26 | { label = "build prototype and evaluate" }, 27 | ] 28 | 29 | [[group]] 30 | name = "rust-analyzer-integration" 31 | label = "Integrate with rust-analyzer" 32 | items = [ 33 | { label = "How to model impl Trait" }, 34 | { label = "Ensure that we never need to ask for impls of unknown types", port = "askfor", requires = ["syntactic-semantic-equality"] }, 35 | { label = "Deal with performance problems" }, 36 | { label = "Deal with memory usage" }, 37 | ] 38 | 39 | [[group]] 40 | name = "syntactic-semantic-equality" 41 | label = "Separate syntactic equality from semantic equality" 42 | requires = ["map-chalk-types-to-rustc-types:debruijn"] 43 | items = [ 44 | ] 45 | 46 | [[group]] 47 | name = "map-chalk-types-to-rustc-types" 48 | label = "Map chalk types to rustc types" 49 | href = "http://example.org" 50 | items = [ 51 | { label = "Move Identifier to TypeFamily" }, 52 | { label = "Adapt rutsc's debruijn index model", port="debruijn" }, 53 | { label = "Remove all vectors, boxes" }, 54 | { label = "Make intern methods take &self" }, 55 | ] 56 | 57 | [[group]] 58 | name = "rustc-integration-mvp" 59 | label = "Integrate chalk-solve into rustc" 60 | requires = [ "map-chalk-types-to-rustc-types" ] 61 | items = [ 62 | { label="remove old chalk support" }, 63 | { label="create" }, 64 | ] 65 | 66 | 67 | --------------------------------------------------------------------------------