aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--extra/dot/example_inbound.dot195
-rw-r--r--extra/dot/example_inbound.pngbin0 -> 699674 bytes
-rw-r--r--extra/dot/levels_inbound.py73
-rw-r--r--skate/.gitignore2
-rw-r--r--skate/Makefile2
-rw-r--r--skate/cmd/skate-dot/main.go80
-rw-r--r--skate/go.mod2
-rw-r--r--skate/go.sum4
-rw-r--r--skate/wordwrap/wordwrap.go83
-rw-r--r--skate/wordwrap/wordwrap_test.go99
10 files changed, 538 insertions, 2 deletions
diff --git a/extra/dot/example_inbound.dot b/extra/dot/example_inbound.dot
new file mode 100644
index 0000000..9beb263
--- /dev/null
+++ b/extra/dot/example_inbound.dot
@@ -0,0 +1,195 @@
+digraph G {
+ node [ style="solid" shape="box" fontname="Arial" fontsize="12" fontcolor="black" ];
+ "fzxizmxeybazhoswj5kvxl7fky" [label="Relating Data\nRefinement\nand Failures-Divergences\nRefinement\n(2018)"];
+ "idt3qiwgffh2rid46v4vh7qh4m" [label="Generality\nin design and\ncompositional\nverification\nusingTav (1995)"];
+ "5hxst5pboja3dntwcx5xmfb6vu" [label="Proving Opacity\nvia Linearizability:\nA Sound and\nComplete Method\n(2017)"];
+ "ki6z42ugjreqzmdfc7hv72ddny" [label="CPL: a core\nlanguage for\ncloud computing\n(2016)"];
+ "vztpo3kn6rgbvgvnyvkpa7r2cu" [label="Reward Testing\nEquivalences\nfor Processes\n(2019)"];
+ "dtdp4dlhmvf2fbw2rsmfdhg7jq" [label="Checking Consistency\nin UML Diagrams:\nClasses and\nState Machines\n(2003)"];
+ "iodn5pvvk5fg3d63nwbs3iqp3i" [label="BSP-Why: A\nTool for Deductive\nVerification\nof BSP Algorithms\nwith Subgroup\nSynchronisation\n(2015)"];
+ "z53wdeubi5egnns7h55wv2vpt4" [label="A proof technique\nfor communicating\nsequential\nprocesses (1981)"];
+ "uddghbfzbbfrtdc6sqirck7pqm" [label="Unified Modelling\nFunctional\nand Non-Functional\nAspects of\nWeb Services\nComposition\nUsing PTCCS\n(2011)"];
+ "kmhwb6yju5gwvoqlirfpzpipka" [label="A FRAMEWORK\nFOR THE EVALUATION\nOF REAL-TIME\nSPECIFICATION\nTECHNIQUES\n(2006)"];
+ "2rqolf6o6jgsdkishicri2wirm" [label="An improved\nfailures model\nfor communicating\nprocesses (1985)"];
+ "lb6746dmrbg75gnbcrv3qavere" [label="TESTOR: A Modular\nTool for On-the-Fly\nConformance\nTest Case Generation\n(2018)"];
+ "ixrau56runetxbqy7olqvods2y" [label="Graphics Related\nActivities\nat NCSDCT,\nBombay (1983)"];
+ "hdwrpnaabzeqvoh6yugn3ti6lm" [label="Vulnerability\nAnalysis of\nCSP Based on\nStochastic\nGame Theory\n(2016)"];
+ "cpq5tedh2jb7zbekcqgdpeigyi" [label="More Automated\nFormal Methods?!\nIf so, why,\nwhere & how?"];
+ "26axhnld75dp3k6ruj7qm6lvbi" [label="Asynchronous\nTesting of\nSynchronous\nComponents\nin GALS Systems\n(2019)"];
+ "erfn3kiztvcvhj2ksskgrzttyy" [label="Calculating\nParallel Programs\nin Coq Using\nList Homomorphisms\n(2016)"];
+ "wwxzdcpcvrcefmw7o25wvwgjau" [label="AXIOMATIC FRAMEWORKS\nFOR DEVELOPING\nBSP-STYLE PROGRAMS∗\n(2000)"];
+ "w6nyuz43fffktacvoq2vnkqwdq" [label="Encoding CSP\ninto CCS (2015)"];
+ "xmc5nch3wfgvjdgfhlu3gfr7ga" [label="Resource-Aware\nVirtually Timed\nAmbients (2018)"];
+ "nazgn3i735e5fabigbyugjm6lu" [label="Correct and\nEfficient Antichain\nAlgorithms\nfor Refinement\nChecking (2019)"];
+ "n2j6o5sjnnc3pgtw23f7pg4uym" [label="Refinement\nand Consistency\nin Component\nModels with\nMultiple Views\n(2006)"];
+ "nqniiaq2cjezfjxs5ezlqfajl4" [label="Taming Concurrency\nfor Verification\nUsing Multiparty\nSession Types\n(2019)"];
+ "atgnqdcm65b33c5uf77el6fvni" [label="A Correctness\nChecking Approach\nfor Collaborative\nBusiness Processes\nin the Cloud\n(2020)"];
+ "mxk2kan4kngqtj5s7zbmpokpua" [label="A Relational\nView of Refinement\n(2018)"];
+ "jqptucpeebfetm77ov32zjtjhy" [label="Reflections\non Bernhard\nSteffen's Physics\nof Software\nTools (2019)"];
+ "hhtadeldyfeudh7zv7li4zarbm" [label="Stronger Validity\nCriteria for\nEncoding Synchrony\n(2019)"];
+ "7w6cjfy7ljfx7i3v2z3hazii3a" [label="Reversibility\nin session-based\nconcurrency:\nA fresh look\n(2017)"];
+ "xup26exlhjbtjnlzs3ya6r5opi" [label="30 Years of\nModal Transition\nSystems: Survey\nof Extensions\nand Analysis\n(2017)"];
+ "2mhbxewq3rczdjjtbpqsl4if4q" [label="Characteristic\nbisimulation\nfor higher-order\nsession processes\n(2016)"];
+ "jq2l5dpeknbqlnyt5gbozszkzy" [label="Communicating\nsequential\nprocesses (1978)" style="filled" fillcolor="orange"];
+ "pgcis54ngfa53o5hawkdeqfria" [label="Hybrid CSP\n(2016)"];
+ "xysudaucabb3flreoy2iztxj6a" [label="FastLane Is\nOpaque – a\nCase Study\nin Mechanized\nProofs of Opacity\n(2018)"];
+ "tyjh5qifcje47pczionoa2dz6y" [label="A state-based\napproach to\ncommunicating\nprocesses (1988)"];
+ "ij7mnzknkjbbxkhy3k2cscxfqy" [label="Coupled similarity:\nthe first 32\nyears (2019)"];
+ "zy2tq4erqrfrborv5d24opidly" [label="Defining and\nVerifying Durable\nOpacity: Correctness\nfor Persistent\nSoftware Transactional\nMemory (2020)"];
+ "cyv5n3dl55hehpef2pizxuqwm4" [label="Validating\nHigh-Level\nSynthesis"];
+ "rbbqg7nhsfdfnaphwfuwqmujvy" [label="Deriving failures\nmodels for\nnonuniform\nconcurrency\nfrom structured\noperational\nsemantics (1996)"];
+ "ipohy4pmfjakfosou7hruin7x4" [label="JDAS: a software\ndevelopment\nframework for\nmultidatabases\n(2017)"];
+ "dm2kmtw5t5aldcbjgwbc6o3pmi" [label="Failure Trace\nSemantics for\na Process Algebra\nwith Time-outs\n(preliminary\nreport) (2020)"];
+ "yctmdvbdxrgglagwiehlhvac3e" [label="Relational\nConcurrent\nRefinement\n(2018)"];
+ "vrcagvkzrzdxnbslhvaignf6si" [label="Component-aware\nInput-Output\nConformance\n(2019)"];
+ "ee4enzkzhvbrha46bzlmerxcsq" [label="Parameterizing\nhigher-order\nprocesses on\nnames and processes\n(2020)"];
+ "7xtcdmozsjh6lppwflvell7qnm" [label="SDN Protocol\nAnalysis with\nProcess Algebra\nMethod (2017)"];
+ "xa33ovyyy5aolab4iuj5oneokq" [label="Specification-oriented\nsemantics for\nCommunicating\nProcesses (1986)"];
+ "yoyygplnt5bxphait6dzdlg534" [label="Checking Modal\nContracts for\nVirtually Timed\nAmbients (2018)"];
+ "gpw35r4invcfln46vulj65f7xm" [label="Modelling Divergence\nin Relational\nConcurrent\nRefinement\n(2009)"];
+ "pdegi4sig5fp5mavy4hqmcugre" [label="On the Expressiveness\nof Symmetric\nCommunication\n(2016)"];
+ "hjtusiibtbho7cxqf56zuuqtg4" [label="A Note on the\nExpressiveness\nof BIP (2016)"];
+ "42c3kolvbvhtdjznoqarjl4574" [label="A Branching\nTime Model\nof CSP (2016)"];
+ "hidk5ja4rfbddp6tgaodiqxcmi" [label="Adopting Formal\nMethods in\nan Industrial\nSetting: The\nRailways Case\n(2019)"];
+ "l2cvg6kg7jabtbtno6gecug6ky" [label="A Branching\nTime Model\nof CSP (2017)"];
+ "mjzpcsji65f2rlmlujanu3nila" [label="Hiding in stream\nsemantics of\nuniform concurrency\n(1990)"];
+ "mfz7bqc5pjgnrojlmsfepz37qe" [label="Musings on\nEncodings and\nExpressiveness\n(2012)"];
+ "2bbo7hkoe5cbblknsd3nwczapa" [label="Expressiveness\nof component-based\nframeworks:\na study of\nthe expressiveness\nof BIP (2019)"];
+ "l6qrrsnysrf2hfmyop4bqhtwma" [label="A Theory of\nEncodings and\nExpressiveness\n(Extended Abstract)\n(2018)"];
+ "e7p4e4juw5fs3fqxyfvwfkc2mq" [label="Step failures\nsemantics and\na complete\nproof system\n(1989)"];
+ "luxpiyqihjbdzevahl6i5lgfi4" [label="Towards safe\nmodular composition\nof network\nfunctions (2018)"];
+ "wf7hn3gqqfdx3epkvh3hzczisy" [label="Evidential\nand Continuous\nIntegration\nof Software\nVerification\nTools (2018)"];
+ "rcm72e4pyvgvnkaixi4t7kypvy" [label="Causally consistent\nreversible\nchoreographies\n(2017)"];
+ "u2tggttxbbbzfdyggh24vuusti" [label="An Approach\nBased on Hierarchical\nPetri Nets\nfor the Verification\nof Interconnected\nBPEL Processes\n(2018)"];
+ "mn2j7unnnfclfdc2w5ajnqxram" [label="Multi-ML: Programming\nMulti-BSP Algorithms\nin ML (2016)"];
+ "y6mx3k2dejdephkuwksc64n2ai" [label="The Challenge\nof Typed Expressiveness\nin Concurrency\n(2016)"];
+ "sydba4xpuff7hdkegjftx3ff5e" [label="Comparing Process\nCalculi Using\nEncodings (2019)"];
+ "k6h2vy5q2nf4tmzgkf2trkppf4" [label="A Calculus\nof Virtually\nTimed Ambients\n(2017)"];
+ "nprtqlxnhrbq3esjlysvismvhy" [label="Transaction\nProtocol Verification\nwith Labeled\nSynchronization\nLogic (2019)"];
+ "b6zqaz2xzvc55bao72xwawsg5u" [label="On Modal Refinement\nand Consistency"];
+ "6px4hrvt65h43n7enhvidbolza" [label="External and\ninternal choice\nwith event\ngroups in Event-B\n(2012)"];
+ "d3qimowb2vdaphditthyex3d3m" [label="Combining Model\nRefinement\nand Test Generation\nfor Conformance\nTesting of\nthe IEEE PHD\nProtocol Using\nAbstract State\nMachines (2019)"];
+ "24n7gsewsngrdkolvalzrs2nhe" [label="Modeling and\nverification\nof Web services\ncomposition\nbased on model\ntransformation\n(2016)"];
+ "qtqir242zva73h363kajbkvsyi" [label="Analysing and\nComparing Encodability\nCriteria (2015)"];
+ "jcx6brqljbcebkizk4cdjeatyu" [label="On the Relative\nExpressiveness\nof Higher-Order\nSession Processes\n(2016)"];
+ "v5htlznfpnex7d2js5zwy47eeu" [label="A Logical Interface\nDescription\nLanguage for\nComponents\n(2000)"];
+ "dpjpdyjd35dmbg774v2kgqb3ge" [label="Formal modeling\nand verification\nof cloud‐based\nweb service\ncomposition\n(2019)"];
+ "x7s3reyu25bixgdgx4y3kwf2ci" [label="Modelling and\nanalysing neural\nnetworks using\na hybrid process\nalgebra (2016)"];
+ "4677h7puyjc2picqlgwdelmybi" [label="On the Expressiveness\nof Joining\n(2015)"];
+ "f6d5clvuifgxhg7fpwrngctccq" [label="Strategy Synthesis\nfor Autonomous\nDriving in\na Moving Block\nRailway System\nwith Uppaal\nStratego (2020)"];
+ "bumvudnas5cqtjs3in7pb4bnua" [label="Computing Coupled\nSimilarity\n(2019)"];
+ "lpqosqmr6bhcbazkvnbythddwy" [label="On Characterising\nDistributability\n(2013)"];
+ "vsmvxmfujndmfbuoz27ygabzf4" [label="Total correctness\nof CSP programs\n(1986)"];
+ "5ouybxooxvf55ibckfuyflm55y" [label="Turing und\ndie Verifikation\n(2012)"];
+ "vzxfucuz45fozhicmxzkxdpdjm" [label="Fifty years\nof Hoare's\nlogic (2019)"];
+ "ir4f556pvvddpbchro7vhffjtq" [label="Applications\nof compactness\nin the Smyth\npowerdomain\nof streams\n(1987)"];
+ "yjywveqbw5g3nhzqrx7ryvmjty" [label="Inductive Proof\nOutlines for\nMonitors in\nJava (2003)"];
+ "tvxk2b6z2jamzn6rsnkq6mjwb4" [label="Simulating\nTruly Concurrent\nCSP (2011)"];
+ "nfgui7zbfbe3tdbso2zmffbwpy" [label="Higher-order\nProcesses with\nParameterization\nover Names\nand Processes\n(2016)"];
+ "hwu7b64kh5brrlm7spzyucxibe" [label="Some recent\nadvances in\nautomated analysis\n(2015)"];
+ "x5amfr7c2zes3cspw32m5my4de" [label="Reversible\nSession-Based\nConcurrency\nin Haskell\n(2019)"];
+ "wxqp6cbl3rbz5jycbasfnski5u" [label="Alan Turing\n– Aus Leben\nund Werk eines\naußergewöhnlichen\nMenschen (2013)"];
+ "ca7g736vorcobkam2244hj2yry" [label="On the Meaning\nof Transition\nSystem Specifications\n(2019)"];
+ "lb4gnwjzqra4tc6on6tz5qsgjy" [label="SAMENVATTING\nIN HET NEDERLANDS"];
+ "lpqosqmr6bhcbazkvnbythddwy" -> "e7p4e4juw5fs3fqxyfvwfkc2mq";
+ "vzxfucuz45fozhicmxzkxdpdjm" -> "5ouybxooxvf55ibckfuyflm55y";
+ "mfz7bqc5pjgnrojlmsfepz37qe" -> "xa33ovyyy5aolab4iuj5oneokq";
+ "pgcis54ngfa53o5hawkdeqfria" -> "xa33ovyyy5aolab4iuj5oneokq";
+ "fzxizmxeybazhoswj5kvxl7fky" -> "tyjh5qifcje47pczionoa2dz6y";
+ "l2cvg6kg7jabtbtno6gecug6ky" -> "xa33ovyyy5aolab4iuj5oneokq";
+ "atgnqdcm65b33c5uf77el6fvni" -> "24n7gsewsngrdkolvalzrs2nhe";
+ "x7s3reyu25bixgdgx4y3kwf2ci" -> "2rqolf6o6jgsdkishicri2wirm";
+ "cyv5n3dl55hehpef2pizxuqwm4" -> "tyjh5qifcje47pczionoa2dz6y";
+ "b6zqaz2xzvc55bao72xwawsg5u" -> "idt3qiwgffh2rid46v4vh7qh4m";
+ "vrcagvkzrzdxnbslhvaignf6si" -> "lb6746dmrbg75gnbcrv3qavere";
+ "zy2tq4erqrfrborv5d24opidly" -> "5hxst5pboja3dntwcx5xmfb6vu";
+ "wxqp6cbl3rbz5jycbasfnski5u" -> "5ouybxooxvf55ibckfuyflm55y";
+ "x5amfr7c2zes3cspw32m5my4de" -> "jcx6brqljbcebkizk4cdjeatyu";
+ "uddghbfzbbfrtdc6sqirck7pqm" -> "jq2l5dpeknbqlnyt5gbozszkzy";
+ "jcx6brqljbcebkizk4cdjeatyu" -> "qtqir242zva73h363kajbkvsyi";
+ "iodn5pvvk5fg3d63nwbs3iqp3i" -> "wwxzdcpcvrcefmw7o25wvwgjau";
+ "42c3kolvbvhtdjznoqarjl4574" -> "2rqolf6o6jgsdkishicri2wirm";
+ "2bbo7hkoe5cbblknsd3nwczapa" -> "mfz7bqc5pjgnrojlmsfepz37qe";
+ "d3qimowb2vdaphditthyex3d3m" -> "lb6746dmrbg75gnbcrv3qavere";
+ "rcm72e4pyvgvnkaixi4t7kypvy" -> "jcx6brqljbcebkizk4cdjeatyu";
+ "luxpiyqihjbdzevahl6i5lgfi4" -> "ki6z42ugjreqzmdfc7hv72ddny";
+ "hhtadeldyfeudh7zv7li4zarbm" -> "qtqir242zva73h363kajbkvsyi";
+ "k6h2vy5q2nf4tmzgkf2trkppf4" -> "ki6z42ugjreqzmdfc7hv72ddny";
+ "hwu7b64kh5brrlm7spzyucxibe" -> "z53wdeubi5egnns7h55wv2vpt4";
+ "mxk2kan4kngqtj5s7zbmpokpua" -> "tyjh5qifcje47pczionoa2dz6y";
+ "wwxzdcpcvrcefmw7o25wvwgjau" -> "z53wdeubi5egnns7h55wv2vpt4";
+ "v5htlznfpnex7d2js5zwy47eeu" -> "xa33ovyyy5aolab4iuj5oneokq";
+ "nprtqlxnhrbq3esjlysvismvhy" -> "5hxst5pboja3dntwcx5xmfb6vu";
+ "pdegi4sig5fp5mavy4hqmcugre" -> "mfz7bqc5pjgnrojlmsfepz37qe";
+ "nazgn3i735e5fabigbyugjm6lu" -> "42c3kolvbvhtdjznoqarjl4574";
+ "e7p4e4juw5fs3fqxyfvwfkc2mq" -> "xa33ovyyy5aolab4iuj5oneokq";
+ "ipohy4pmfjakfosou7hruin7x4" -> "24n7gsewsngrdkolvalzrs2nhe";
+ "yjywveqbw5g3nhzqrx7ryvmjty" -> "z53wdeubi5egnns7h55wv2vpt4";
+ "ij7mnzknkjbbxkhy3k2cscxfqy" -> "qtqir242zva73h363kajbkvsyi";
+ "vzxfucuz45fozhicmxzkxdpdjm" -> "z53wdeubi5egnns7h55wv2vpt4";
+ "24n7gsewsngrdkolvalzrs2nhe" -> "uddghbfzbbfrtdc6sqirck7pqm";
+ "bumvudnas5cqtjs3in7pb4bnua" -> "42c3kolvbvhtdjznoqarjl4574";
+ "xmc5nch3wfgvjdgfhlu3gfr7ga" -> "k6h2vy5q2nf4tmzgkf2trkppf4";
+ "z53wdeubi5egnns7h55wv2vpt4" -> "jq2l5dpeknbqlnyt5gbozszkzy";
+ "y6mx3k2dejdephkuwksc64n2ai" -> "jcx6brqljbcebkizk4cdjeatyu";
+ "yctmdvbdxrgglagwiehlhvac3e" -> "tyjh5qifcje47pczionoa2dz6y";
+ "hhtadeldyfeudh7zv7li4zarbm" -> "l6qrrsnysrf2hfmyop4bqhtwma";
+ "42c3kolvbvhtdjznoqarjl4574" -> "xa33ovyyy5aolab4iuj5oneokq";
+ "sydba4xpuff7hdkegjftx3ff5e" -> "mfz7bqc5pjgnrojlmsfepz37qe";
+ "mjzpcsji65f2rlmlujanu3nila" -> "xa33ovyyy5aolab4iuj5oneokq";
+ "ixrau56runetxbqy7olqvods2y" -> "jq2l5dpeknbqlnyt5gbozszkzy";
+ "hjtusiibtbho7cxqf56zuuqtg4" -> "mfz7bqc5pjgnrojlmsfepz37qe";
+ "lb4gnwjzqra4tc6on6tz5qsgjy" -> "e7p4e4juw5fs3fqxyfvwfkc2mq";
+ "qtqir242zva73h363kajbkvsyi" -> "mfz7bqc5pjgnrojlmsfepz37qe";
+ "lb6746dmrbg75gnbcrv3qavere" -> "jq2l5dpeknbqlnyt5gbozszkzy";
+ "2rqolf6o6jgsdkishicri2wirm" -> "z53wdeubi5egnns7h55wv2vpt4";
+ "hdwrpnaabzeqvoh6yugn3ti6lm" -> "jq2l5dpeknbqlnyt5gbozszkzy";
+ "cpq5tedh2jb7zbekcqgdpeigyi" -> "hwu7b64kh5brrlm7spzyucxibe";
+ "mn2j7unnnfclfdc2w5ajnqxram" -> "iodn5pvvk5fg3d63nwbs3iqp3i";
+ "f6d5clvuifgxhg7fpwrngctccq" -> "hidk5ja4rfbddp6tgaodiqxcmi";
+ "gpw35r4invcfln46vulj65f7xm" -> "tyjh5qifcje47pczionoa2dz6y";
+ "tvxk2b6z2jamzn6rsnkq6mjwb4" -> "e7p4e4juw5fs3fqxyfvwfkc2mq";
+ "jqptucpeebfetm77ov32zjtjhy" -> "lb6746dmrbg75gnbcrv3qavere";
+ "dtdp4dlhmvf2fbw2rsmfdhg7jq" -> "tyjh5qifcje47pczionoa2dz6y";
+ "5hxst5pboja3dntwcx5xmfb6vu" -> "jq2l5dpeknbqlnyt5gbozszkzy";
+ "mfz7bqc5pjgnrojlmsfepz37qe" -> "2rqolf6o6jgsdkishicri2wirm";
+ "6px4hrvt65h43n7enhvidbolza" -> "tyjh5qifcje47pczionoa2dz6y";
+ "wf7hn3gqqfdx3epkvh3hzczisy" -> "hwu7b64kh5brrlm7spzyucxibe";
+ "dm2kmtw5t5aldcbjgwbc6o3pmi" -> "l6qrrsnysrf2hfmyop4bqhtwma";
+ "yoyygplnt5bxphait6dzdlg534" -> "k6h2vy5q2nf4tmzgkf2trkppf4";
+ "tyjh5qifcje47pczionoa2dz6y" -> "2rqolf6o6jgsdkishicri2wirm";
+ "ir4f556pvvddpbchro7vhffjtq" -> "mjzpcsji65f2rlmlujanu3nila";
+ "hidk5ja4rfbddp6tgaodiqxcmi" -> "jqptucpeebfetm77ov32zjtjhy";
+ "vsmvxmfujndmfbuoz27ygabzf4" -> "z53wdeubi5egnns7h55wv2vpt4";
+ "kmhwb6yju5gwvoqlirfpzpipka" -> "jq2l5dpeknbqlnyt5gbozszkzy";
+ "n2j6o5sjnnc3pgtw23f7pg4uym" -> "tyjh5qifcje47pczionoa2dz6y";
+ "4677h7puyjc2picqlgwdelmybi" -> "mfz7bqc5pjgnrojlmsfepz37qe";
+ "jcx6brqljbcebkizk4cdjeatyu" -> "mfz7bqc5pjgnrojlmsfepz37qe";
+ "dpjpdyjd35dmbg774v2kgqb3ge" -> "24n7gsewsngrdkolvalzrs2nhe";
+ "w6nyuz43fffktacvoq2vnkqwdq" -> "qtqir242zva73h363kajbkvsyi";
+ "ij7mnzknkjbbxkhy3k2cscxfqy" -> "42c3kolvbvhtdjznoqarjl4574";
+ "nqniiaq2cjezfjxs5ezlqfajl4" -> "qtqir242zva73h363kajbkvsyi";
+ "hhtadeldyfeudh7zv7li4zarbm" -> "mfz7bqc5pjgnrojlmsfepz37qe";
+ "7w6cjfy7ljfx7i3v2z3hazii3a" -> "jcx6brqljbcebkizk4cdjeatyu";
+ "ee4enzkzhvbrha46bzlmerxcsq" -> "jcx6brqljbcebkizk4cdjeatyu";
+ "ca7g736vorcobkam2244hj2yry" -> "l6qrrsnysrf2hfmyop4bqhtwma";
+ "bumvudnas5cqtjs3in7pb4bnua" -> "qtqir242zva73h363kajbkvsyi";
+ "xup26exlhjbtjnlzs3ya6r5opi" -> "idt3qiwgffh2rid46v4vh7qh4m";
+ "sydba4xpuff7hdkegjftx3ff5e" -> "qtqir242zva73h363kajbkvsyi";
+ "rbbqg7nhsfdfnaphwfuwqmujvy" -> "2rqolf6o6jgsdkishicri2wirm";
+ "5ouybxooxvf55ibckfuyflm55y" -> "z53wdeubi5egnns7h55wv2vpt4";
+ "26axhnld75dp3k6ruj7qm6lvbi" -> "lb6746dmrbg75gnbcrv3qavere";
+ "nazgn3i735e5fabigbyugjm6lu" -> "2rqolf6o6jgsdkishicri2wirm";
+ "ki6z42ugjreqzmdfc7hv72ddny" -> "jq2l5dpeknbqlnyt5gbozszkzy";
+ "xysudaucabb3flreoy2iztxj6a" -> "5hxst5pboja3dntwcx5xmfb6vu";
+ "l6qrrsnysrf2hfmyop4bqhtwma" -> "qtqir242zva73h363kajbkvsyi";
+ "nfgui7zbfbe3tdbso2zmffbwpy" -> "jcx6brqljbcebkizk4cdjeatyu";
+ "2mhbxewq3rczdjjtbpqsl4if4q" -> "jcx6brqljbcebkizk4cdjeatyu";
+ "erfn3kiztvcvhj2ksskgrzttyy" -> "iodn5pvvk5fg3d63nwbs3iqp3i";
+ "xa33ovyyy5aolab4iuj5oneokq" -> "2rqolf6o6jgsdkishicri2wirm";
+ "7xtcdmozsjh6lppwflvell7qnm" -> "x7s3reyu25bixgdgx4y3kwf2ci";
+ "vztpo3kn6rgbvgvnyvkpa7r2cu" -> "xa33ovyyy5aolab4iuj5oneokq";
+ "u2tggttxbbbzfdyggh24vuusti" -> "24n7gsewsngrdkolvalzrs2nhe";
+ "sydba4xpuff7hdkegjftx3ff5e" -> "l6qrrsnysrf2hfmyop4bqhtwma";
+ "idt3qiwgffh2rid46v4vh7qh4m" -> "2rqolf6o6jgsdkishicri2wirm";
+}
diff --git a/extra/dot/example_inbound.png b/extra/dot/example_inbound.png
new file mode 100644
index 0000000..98a848d
--- /dev/null
+++ b/extra/dot/example_inbound.png
Binary files differ
diff --git a/extra/dot/levels_inbound.py b/extra/dot/levels_inbound.py
new file mode 100644
index 0000000..290ab6f
--- /dev/null
+++ b/extra/dot/levels_inbound.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+"""
+Take a document fatcat id and emit the citation structure up to X levels as graphviz dot file.
+"""
+
+from elasticsearch import Elasticsearch
+from elasticsearch_dsl import Search
+import sys
+import requests
+
+seed = sys.argv[1] if len(sys.argv) > 1 else "rgzyfbx7t5d53m5amvbllewzqi"
+max_nodes = 50
+
+client = Elasticsearch("http://localhost:9200")
+S = Search(using=client, index="fatcat_ref_v01")
+
+def pretty_label(s, k=10):
+ parts = s.split()
+ cur, result =[], []
+ for p in parts:
+ if len(" ".join(cur)) < k:
+ cur.append(p)
+ else:
+ result.append(" ".join(cur))
+ cur = []
+ result.append(" ".join(cur))
+ return "\\n".join(result)
+
+def get_dot_label(ident):
+ title = requests.get("https://api.fatcat.wiki/v0/release/{}".format(ident)).json().get("title")
+ return pretty_label(title, k=10)
+
+
+def unique_sources(ident):
+ """
+ Unique inbound references.
+ """
+ s = S.query("match", target_release_ident=ident)
+ unique_source_idents = set()
+ for hit in s.scan():
+ unique_source_idents.add(hit["source_release_ident"])
+ return list(unique_source_idents)
+
+queue = set([seed])
+i = 0
+edges = set()
+while queue:
+ node = queue.pop()
+ i += 1
+ if i == max_nodes:
+ break
+ for n in unique_sources(node)[:5]:
+ if n == node:
+ print("skipping self ref: {}".format(n), file=sys.stderr)
+ continue
+ edges.add((node, n))
+ queue.add(n)
+
+nodes = set()
+for a, b in edges:
+ nodes.add(a)
+ nodes.add(b)
+
+print("digraph G {")
+# add sensible labels
+for n in nodes:
+ print(""" "{}" [label="{}"]; """.format(n, get_dot_label(n)))
+
+for a, b in edges:
+ print(""" "{}" -> "{}"; """.format(a, b))
+print("}")
+
diff --git a/skate/.gitignore b/skate/.gitignore
index 4e893a0..031329a 100644
--- a/skate/.gitignore
+++ b/skate/.gitignore
@@ -24,4 +24,4 @@
/skate-wikipedia-doi
packaging/debian/skate/usr
skate_*_amd64.deb
-
+/skate-dot
diff --git a/skate/Makefile b/skate/Makefile
index ccaf08e..fc36c8e 100644
--- a/skate/Makefile
+++ b/skate/Makefile
@@ -1,5 +1,5 @@
SHELL := /bin/bash
-TARGETS := skate-ref-to-release skate-derive-key skate-cluster skate-verify skate-to-doi skate-bref-id skate-from-unstructured skate-wikipedia-doi
+TARGETS := skate-ref-to-release skate-derive-key skate-cluster skate-verify skate-to-doi skate-bref-id skate-from-unstructured skate-wikipedia-doi skate-dot
PKGNAME := skate
.PHONY: test
diff --git a/skate/cmd/skate-dot/main.go b/skate/cmd/skate-dot/main.go
new file mode 100644
index 0000000..97b70ad
--- /dev/null
+++ b/skate/cmd/skate-dot/main.go
@@ -0,0 +1,80 @@
+// skate-dot generates dot files from inbound and outbound citation links.
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+
+ "github.com/elastic/go-elasticsearch/esapi"
+ elasticsearch "github.com/elastic/go-elasticsearch/v7"
+)
+
+var (
+ es = flag.String("es", "http://localhost:9200", "elasticsearch holding fatcat_ref index")
+ index = flag.String("x", "fatcat_ref_v01", "index name")
+ fatcat = flag.String("f", "https://api.fatcat.wiki/v0", "fatcat api")
+ ident = flag.String("i", "2kw3xjf2cbcmdlm3ihkoz2t4lu", "release ident")
+)
+
+func main() {
+ flag.Parse()
+ // s := "The Determination of Concentration and Type of Ownership on Bank Performance and Risks in Indonesia"
+ // fmt.Printf("%s\n", wordwrap.WrapString(s, 20))
+ cfg := elasticsearch.Config{
+ Addresses: []string{
+ *es,
+ },
+ }
+ es, err := elasticsearch.NewClient(cfg)
+ if err != nil {
+ log.Fatal(err)
+ }
+ client := &Client{
+ Api: *fatcat,
+ Es: es,
+ Index: *index,
+ }
+ log.Println(client)
+ client.Outbound(*ident)
+ client.Inbound(*ident)
+}
+
+// A client for fatcat and elasticsearch.
+type Client struct {
+ Api string
+ Es *elasticsearch.Client
+ Index string
+}
+
+func (c *Client) String() string {
+ info, _ := c.Es.Info()
+ return fmt.Sprintf("%s %s (%s) %s", c.Api, info, elasticsearch.Version, c.Index)
+}
+
+func (c *Client) Inbound(ident string) []string {
+ resp, err := esapi.Search(
+ esapi.Search.WithContext(context.Background()),
+ esapi.Search.WithIndex(c.Index),
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+ io.Copy(os.Stdout, resp.Body)
+ return nil
+}
+
+func (c *Client) Outbound(ident string) []string {
+ req := &esapi.SearchRequest{
+ Query: fmt.Sprintf("source_release_ident:%s", ident),
+ }
+ resp, err := req.Do(context.Background(), c.Es)
+ if err != nil {
+ log.Fatal(err)
+ }
+ io.Copy(os.Stdout, resp.Body)
+ return nil
+}
diff --git a/skate/go.mod b/skate/go.mod
index e8678c0..c14fd53 100644
--- a/skate/go.mod
+++ b/skate/go.mod
@@ -5,6 +5,8 @@ go 1.15
require (
github.com/colinmarc/hdfs v1.1.3 // indirect
github.com/dgraph-io/ristretto v0.0.3
+ github.com/elastic/go-elasticsearch v0.0.0 // indirect
+ github.com/elastic/go-elasticsearch/v7 v7.12.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/json-iterator/go v1.1.10
github.com/kr/pretty v0.2.1 // indirect
diff --git a/skate/go.sum b/skate/go.sum
index 8690252..1d77ab9 100644
--- a/skate/go.sum
+++ b/skate/go.sum
@@ -11,6 +11,10 @@ github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDz
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/elastic/go-elasticsearch v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA=
+github.com/elastic/go-elasticsearch v0.0.0/go.mod h1:TkBSJBuTyFdBnrNqoPc54FN0vKf5c04IdM4zuStJ7xg=
+github.com/elastic/go-elasticsearch/v7 v7.12.0 h1:j4tvcMrZJLp39L2NYvBb7f+lHKPqPHSL3nvB8+/DV+s=
+github.com/elastic/go-elasticsearch/v7 v7.12.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
diff --git a/skate/wordwrap/wordwrap.go b/skate/wordwrap/wordwrap.go
new file mode 100644
index 0000000..f7bedda
--- /dev/null
+++ b/skate/wordwrap/wordwrap.go
@@ -0,0 +1,83 @@
+package wordwrap
+
+import (
+ "bytes"
+ "unicode"
+)
+
+const nbsp = 0xA0
+
+// WrapString wraps the given string within lim width in characters.
+//
+// Wrapping is currently naive and only happens at white-space. A future
+// version of the library will implement smarter wrapping. This means that
+// pathological cases can dramatically reach past the limit, such as a very
+// long word.
+func WrapString(s string, lim uint) string {
+ // Initialize a buffer with a slightly larger size to account for breaks
+ init := make([]byte, 0, len(s))
+ buf := bytes.NewBuffer(init)
+
+ var current uint
+ var wordBuf, spaceBuf bytes.Buffer
+ var wordBufLen, spaceBufLen uint
+
+ for _, char := range s {
+ if char == '\n' {
+ if wordBuf.Len() == 0 {
+ if current+spaceBufLen > lim {
+ current = 0
+ } else {
+ current += spaceBufLen
+ spaceBuf.WriteTo(buf)
+ }
+ spaceBuf.Reset()
+ spaceBufLen = 0
+ } else {
+ current += spaceBufLen + wordBufLen
+ spaceBuf.WriteTo(buf)
+ spaceBuf.Reset()
+ spaceBufLen = 0
+ wordBuf.WriteTo(buf)
+ wordBuf.Reset()
+ wordBufLen = 0
+ }
+ buf.WriteRune(char)
+ current = 0
+ } else if unicode.IsSpace(char) && char != nbsp {
+ if spaceBuf.Len() == 0 || wordBuf.Len() > 0 {
+ current += spaceBufLen + wordBufLen
+ spaceBuf.WriteTo(buf)
+ spaceBuf.Reset()
+ spaceBufLen = 0
+ wordBuf.WriteTo(buf)
+ wordBuf.Reset()
+ wordBufLen = 0
+ }
+
+ spaceBuf.WriteRune(char)
+ spaceBufLen++
+ } else {
+ wordBuf.WriteRune(char)
+ wordBufLen++
+
+ if current+wordBufLen+spaceBufLen > lim && wordBufLen < lim {
+ buf.WriteRune('\n')
+ current = 0
+ spaceBuf.Reset()
+ spaceBufLen = 0
+ }
+ }
+ }
+
+ if wordBuf.Len() == 0 {
+ if current+spaceBufLen <= lim {
+ spaceBuf.WriteTo(buf)
+ }
+ } else {
+ spaceBuf.WriteTo(buf)
+ wordBuf.WriteTo(buf)
+ }
+
+ return buf.String()
+}
diff --git a/skate/wordwrap/wordwrap_test.go b/skate/wordwrap/wordwrap_test.go
new file mode 100644
index 0000000..98010eb
--- /dev/null
+++ b/skate/wordwrap/wordwrap_test.go
@@ -0,0 +1,99 @@
+package wordwrap
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestWrapString(t *testing.T) {
+ cases := []struct {
+ Input, Output string
+ Lim uint
+ }{
+ // A simple word passes through.
+ {
+ "foo",
+ "foo",
+ 4,
+ },
+ // A single word that is too long passes through.
+ // We do not break words.
+ {
+ "foobarbaz",
+ "foobarbaz",
+ 4,
+ },
+ // Lines are broken at whitespace.
+ {
+ "foo bar baz",
+ "foo\nbar\nbaz",
+ 4,
+ },
+ // Lines are broken at whitespace, even if words
+ // are too long. We do not break words.
+ {
+ "foo bars bazzes",
+ "foo\nbars\nbazzes",
+ 4,
+ },
+ // A word that would run beyond the width is wrapped.
+ {
+ "fo sop",
+ "fo\nsop",
+ 4,
+ },
+ // Do not break on non-breaking space.
+ {
+ "foo bar\u00A0baz",
+ "foo\nbar\u00A0baz",
+ 10,
+ },
+ // Whitespace that trails a line and fits the width
+ // passes through, as does whitespace prefixing an
+ // explicit line break. A tab counts as one character.
+ {
+ "foo\nb\t r\n baz",
+ "foo\nb\t r\n baz",
+ 4,
+ },
+ // Trailing whitespace is removed if it doesn't fit the width.
+ // Runs of whitespace on which a line is broken are removed.
+ {
+ "foo \nb ar ",
+ "foo\nb\nar",
+ 4,
+ },
+ // An explicit line break at the end of the input is preserved.
+ {
+ "foo bar baz\n",
+ "foo\nbar\nbaz\n",
+ 4,
+ },
+ // Explicit break are always preserved.
+ {
+ "\nfoo bar\n\n\nbaz\n",
+ "\nfoo\nbar\n\n\nbaz\n",
+ 4,
+ },
+ // Complete example:
+ {
+ " This is a list: \n\n\t* foo\n\t* bar\n\n\n\t* baz \nBAM ",
+ " This\nis a\nlist: \n\n\t* foo\n\t* bar\n\n\n\t* baz\nBAM",
+ 6,
+ },
+ // Multi-byte characters
+ {
+ strings.Repeat("\u2584 ", 4),
+ "\u2584 \u2584" + "\n" +
+ strings.Repeat("\u2584 ", 2),
+ 4,
+ },
+ }
+
+ for i, tc := range cases {
+ actual := WrapString(tc.Input, tc.Lim)
+ if actual != tc.Output {
+ t.Fatalf("Case %d Input:\n\n`%s`\n\nExpected Output:\n\n`%s`\n\nActual Output:\n\n`%s`", i, tc.Input, tc.Output, actual)
+ }
+ }
+}